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);
}