using System; using System.Runtime.InteropServices; using Avalonia; using Avalonia.Controls; using Godosa.Core.Diagnostics; namespace Godosa.Desktop; /// /// Moves the OS pointer to a point in a visual (free look recentering, camera /// spec ยง3: Avalonia 12.1.2 has no pointer lock). X11/XWayland via libX11 /// (XWayland turns a warp under a hidden cursor into a pointer lock), Windows /// via user32. Returns false when unsupported or on any failure. /// public static partial class CursorWarp { private static IntPtr _display; public static bool TryWarp(Visual visual, Point viewPoint) { try { var topLevel = TopLevel.GetTopLevel(visual); var handle = topLevel?.TryGetPlatformHandle(); if (topLevel is null || handle is null) return false; if (OperatingSystem.IsWindows()) { var screen = visual.PointToScreen(viewPoint); return SetCursorPos(screen.X, screen.Y); } if (OperatingSystem.IsLinux() && handle.HandleDescriptor == "XID") { if (visual.TranslatePoint(viewPoint, topLevel) is not { } p) return false; if (_display == IntPtr.Zero) _display = XOpenDisplay(IntPtr.Zero); if (_display == IntPtr.Zero) return false; double scaling = topLevel.RenderScaling; XWarpPointer(_display, 0, (nuint)handle.Handle, 0, 0, 0, 0, (int)Math.Round(p.X * scaling), (int)Math.Round(p.Y * scaling)); XFlush(_display); return true; } if (EngineDebug.Input) EngineDebug.Write("input", $"cursor warp unsupported (handle {handle.HandleDescriptor})"); return false; } catch (Exception ex) when (ex is DllNotFoundException or EntryPointNotFoundException) { if (EngineDebug.Input) EngineDebug.Write("input", $"cursor warp failed: {ex.Message}"); return false; } } [LibraryImport("libX11.so.6")] private static partial IntPtr XOpenDisplay(IntPtr displayName); [LibraryImport("libX11.so.6")] private static partial int XWarpPointer(IntPtr display, nuint srcWindow, nuint destWindow, int srcX, int srcY, uint srcWidth, uint srcHeight, int destX, int destY); [LibraryImport("libX11.so.6")] private static partial int XFlush(IntPtr display); [LibraryImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool SetCursorPos(int x, int y); }