aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Desktop/CursorWarp.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Godosa.Desktop/CursorWarp.cs')
-rw-r--r--src/Godosa.Desktop/CursorWarp.cs66
1 files changed, 66 insertions, 0 deletions
diff --git a/src/Godosa.Desktop/CursorWarp.cs b/src/Godosa.Desktop/CursorWarp.cs
new file mode 100644
index 0000000..c44342d
--- /dev/null
+++ b/src/Godosa.Desktop/CursorWarp.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Runtime.InteropServices;
+using Avalonia;
+using Avalonia.Controls;
+using Godosa.Core.Diagnostics;
+
+namespace Godosa.Desktop;
+
+/// <summary>
+/// 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.
+/// </summary>
+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);
+}