diff --git a/README.md b/README.md index 8c4646f..6828521 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/YMouseButtonControl.Linux/Services/CurrentWindowServiceX11.cs b/YMouseButtonControl.Linux/Services/CurrentWindowServiceX11.cs index 45e7c8d..2fa5d8a 100644 --- a/YMouseButtonControl.Linux/Services/CurrentWindowServiceX11.cs +++ b/YMouseButtonControl.Linux/Services/CurrentWindowServiceX11.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Runtime.InteropServices; using YMouseButtonControl.Core.Services.Processes; namespace YMouseButtonControl.Linux.Services; @@ -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 + ); +} diff --git a/YMouseButtonControl.Linux/YMouseButtonControl.Linux.csproj b/YMouseButtonControl.Linux/YMouseButtonControl.Linux.csproj index 72e9088..fa29fe5 100644 --- a/YMouseButtonControl.Linux/YMouseButtonControl.Linux.csproj +++ b/YMouseButtonControl.Linux/YMouseButtonControl.Linux.csproj @@ -5,6 +5,7 @@ enable enable default + true diff --git a/YMouseButtonControl/DependencyInjection/ServicesBootstrapper.cs b/YMouseButtonControl/DependencyInjection/ServicesBootstrapper.cs index 94b21b6..cad166b 100644 --- a/YMouseButtonControl/DependencyInjection/ServicesBootstrapper.cs +++ b/YMouseButtonControl/DependencyInjection/ServicesBootstrapper.cs @@ -55,25 +55,7 @@ private static void RegisterLinuxServices(IServiceCollection services) .AddScoped(); 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(); - } - else - { - services.AddScoped(); - } + services.AddScoped(); } else {