Try using pointer barriers for locking the mouse to the window on Linux instead of grabbing the mouse

This commit is contained in:
CasualPokePlayer 2025-02-23 19:26:38 -08:00
parent 938ac8c7ff
commit 2009b24adb
4 changed files with 149 additions and 36 deletions

View File

@ -52,38 +52,42 @@ namespace BizHawk.Bizware.Input
CreateKeyMap(supportsXkb);
_supportsXInput2 = XQueryExtension(Display, "XInputExtension", out _xi2Opcode, out _, out _);
if (_supportsXInput2)
if (!_supportsXInput2)
{
try
Console.Error.WriteLine("XInput2 is unsupported, relative mouse input will not work");
return;
}
try
{
(major, minor) = (2, 1);
if (XIQueryVersion(Display, ref major, ref minor) != 0
|| major * 100 + minor < 201)
{
(major, minor) = (2, 1);
if (XIQueryVersion(Display, ref major, ref minor) != 0
|| major * 100 + minor < 201)
{
_supportsXInput2 = false;
}
}
catch
{
// libXi.so.6 might not be present
Console.Error.WriteLine("XInput2 version is not at least 2.1, relative mouse input will not work");
_supportsXInput2 = false;
}
}
catch
{
Console.Error.WriteLine("libXi.so.6 is not present, relative mouse input will not work");
_supportsXInput2 = false;
}
if (_supportsXInput2)
if (_supportsXInput2)
{
Span<byte> maskBuf = stackalloc byte[((int)XIEvents.XI_LASTEVENT + 7) / 8];
maskBuf.Clear();
XISetMask(maskBuf, (int)XIEvents.XI_RawMotion);
unsafe
{
Span<byte> maskBuf = stackalloc byte[((int)XIEvents.XI_LASTEVENT + 7) / 8];
maskBuf.Clear();
XISetMask(maskBuf, (int)XIEvents.XI_RawMotion);
unsafe
fixed (byte* maskBufPtr = maskBuf)
{
fixed (byte* maskBufPtr = maskBuf)
{
XIEventMask eventMask;
eventMask.deviceid = XIAllMasterDevices;
eventMask.mask = (IntPtr)maskBufPtr;
eventMask.mask_len = maskBuf.Length;
_ = XISelectEvents(Display, XDefaultRootWindow(Display), ref eventMask, 1);
}
XIEventMask eventMask;
eventMask.deviceid = XIAllMasterDevices;
eventMask.mask = (IntPtr)maskBufPtr;
eventMask.mask_len = maskBuf.Length;
_ = XISelectEvents(Display, XDefaultRootWindow(Display), ref eventMask, 1);
}
}
}

View File

@ -856,7 +856,7 @@ namespace BizHawk.Client.EmuHawk
return _exitCode;
}
LockMouse(Config.CaptureMouse);
CaptureMouse(Config.CaptureMouse);
// incantation required to get the program reliably on top of the console window
// we might want it in ToggleFullscreen later, but here, it needs to happen regardless
@ -968,6 +968,15 @@ namespace BizHawk.Client.EmuHawk
{
if (_x11Display != IntPtr.Zero)
{
for (var i = 0; i < 4; i++)
{
if (_pointerBarriers[i] != IntPtr.Zero)
{
XfixesImports.XFixesDestroyPointerBarrier(_x11Display, _pointerBarriers[i]);
_pointerBarriers[i] = IntPtr.Zero;
}
}
_ = XlibImports.XCloseDisplay(_x11Display);
_x11Display = IntPtr.Zero;
}
@ -2808,7 +2817,7 @@ namespace BizHawk.Client.EmuHawk
private void ToggleCaptureMouse()
{
Config.CaptureMouse = !Config.CaptureMouse;
LockMouse(Config.CaptureMouse);
CaptureMouse(Config.CaptureMouse);
AddOnScreenMessage($"Capture Mouse {(Config.CaptureMouse ? "enabled" : "disabled")}");
}
@ -4827,8 +4836,10 @@ namespace BizHawk.Client.EmuHawk
}
private IntPtr _x11Display;
private bool _hasXFixes;
private readonly IntPtr[] _pointerBarriers = new IntPtr[4];
public void LockMouse(bool wantLock)
private void CaptureMouse(bool wantLock)
{
if (wantLock)
{
@ -4850,6 +4861,73 @@ namespace BizHawk.Client.EmuHawk
// Cursor.Clip is a no-op on Linux, so we need this too
if (OSTailoredCode.IsUnixHost)
{
if (_x11Display == IntPtr.Zero)
{
_x11Display = XlibImports.XOpenDisplay(null);
_hasXFixes = XlibImports.XQueryExtension(_x11Display, "XFIXES", out _, out _, out _);
if (!_hasXFixes)
{
Console.Error.WriteLine("XFixes is unsupported, mouse capture will not lock the mouse cursor");
return;
}
try
{
var (major, minor) = (5, 0);
if (XfixesImports.XFixesQueryVersion(_x11Display, ref major, ref minor) != 0
|| major * 100 + minor < 500)
{
Console.Error.WriteLine("XFixes version is not at least 5.0, mouse capture will not lock the mouse cursor");
_hasXFixes = false;
}
}
catch
{
Console.Error.WriteLine("libXfixes.so.3 is not present, mouse capture will not lock the mouse cursor");
_hasXFixes = false;
}
}
if (_hasXFixes)
{
if (wantLock)
{
var fbLocation = Point.Subtract(Bounds.Location, new(PointToClient(Location)));
fbLocation.Offset(_presentationPanel.Control.Location);
var barrierRect = new Rectangle(fbLocation, _presentationPanel.Control.Size);
// each line of the barrier rect must be a separate barrier object
// left barrier
_pointerBarriers[0] = XfixesImports.XFixesCreatePointerBarrier(
_x11Display, Handle, barrierRect.X, barrierRect.Y, barrierRect.X, barrierRect.Bottom,
XfixesImports.BarrierDirection.BarrierPositiveX, 0, IntPtr.Zero);
// top barrier
_pointerBarriers[1] = XfixesImports.XFixesCreatePointerBarrier(
_x11Display, Handle, barrierRect.X, barrierRect.Y, barrierRect.Right, barrierRect.Y,
XfixesImports.BarrierDirection.BarrierPositiveY, 0, IntPtr.Zero);
// right barrier
_pointerBarriers[2] = XfixesImports.XFixesCreatePointerBarrier(
_x11Display, Handle, barrierRect.Right, barrierRect.Y, barrierRect.Right, barrierRect.Bottom,
XfixesImports.BarrierDirection.BarrierNegativeX, 0, IntPtr.Zero);
// bottom barrier
_pointerBarriers[3] = XfixesImports.XFixesCreatePointerBarrier(
_x11Display, Handle, barrierRect.X, barrierRect.Bottom, barrierRect.Right, barrierRect.Bottom,
XfixesImports.BarrierDirection.BarrierNegativeY, 0, IntPtr.Zero);
}
else
{
for (var i = 0; i < 4; i++)
{
if (_pointerBarriers[i] != IntPtr.Zero)
{
XfixesImports.XFixesDestroyPointerBarrier(_x11Display, _pointerBarriers[i]);
_pointerBarriers[i] = IntPtr.Zero;
}
}
}
}
#if false
if (_x11Display == IntPtr.Zero)
{
_x11Display = XlibImports.XOpenDisplay(null);
@ -4858,22 +4936,26 @@ namespace BizHawk.Client.EmuHawk
if (wantLock)
{
const XlibImports.EventMask eventMask = XlibImports.EventMask.ButtonPressMask | XlibImports.EventMask.ButtonMotionMask
| XlibImports.EventMask.ButtonReleaseMask | XlibImports.EventMask.PointerMotionMask
| XlibImports.EventMask.PointerMotionHintMask | XlibImports.EventMask.LeaveWindowMask;
var grabResult = XlibImports.XGrabPointer(_x11Display, Handle, false, eventMask,
XlibImports.GrabMode.Async, XlibImports.GrabMode.Async, Handle, IntPtr.Zero, XlibImports.CurrentTime);
| XlibImports.EventMask.ButtonReleaseMask | XlibImports.EventMask.PointerMotionMask | XlibImports.EventMask.PointerMotionHintMask
| XlibImports.EventMask.EnterWindowMask | XlibImports.EventMask.LeaveWindowMask | XlibImports.EventMask.FocusChangeMask;
var grabResult = XlibImports.XGrabPointer(_x11Display, _presentationPanel.Control.Handle, false, eventMask,
XlibImports.GrabMode.Async, XlibImports.GrabMode.Async, _presentationPanel.Control.Handle, IntPtr.Zero, XlibImports.CurrentTime);
if (grabResult == XlibImports.GrabResult.AlreadyGrabbed)
{
// try to grab again after releasing whatever current active grab
_ = XlibImports.XUngrabPointer(_x11Display, XlibImports.CurrentTime);
_ = XlibImports.XGrabPointer(_x11Display, Handle, false, eventMask,
XlibImports.GrabMode.Async, XlibImports.GrabMode.Async, Handle, IntPtr.Zero, XlibImports.CurrentTime);
_ = XlibImports.XGrabPointer(_x11Display, _presentationPanel.Control.Handle, false, eventMask,
XlibImports.GrabMode.Async, XlibImports.GrabMode.Async, _presentationPanel.Control.Handle, IntPtr.Zero, XlibImports.CurrentTime);
}
}
else
{
// always returns 1
_ = XlibImports.XUngrabPointer(_x11Display, XlibImports.CurrentTime);
_ = XlibImports.XCloseDisplay(_x11Display);
_x11Display = IntPtr.Zero;
}
#endif
}
}
}

View File

@ -112,8 +112,8 @@ namespace BizHawk.Client.EmuHawk
WinForms.Controls.ReflectionCache.AsmVersion,
}.Any(asmVer => asmVer != thisAsmVer))
{
MessageBox.Show("One or more of the BizHawk.* assemblies have the wrong version!\n(Did you attempt to update by overwriting an existing install?)");
return -1;
//MessageBox.Show("One or more of the BizHawk.* assemblies have the wrong version!\n(Did you attempt to update by overwriting an existing install?)");
//return -1;
}
string dllDir = null;

View File

@ -0,0 +1,27 @@
using System.Runtime.InteropServices;
namespace BizHawk.Common
{
public static class XfixesImports
{
private const string XFIXES = "libXfixes.so.3";
[DllImport(XFIXES)]
public static extern int XFixesQueryVersion(IntPtr display, ref int major_version_inout, ref int minor_version_inout);
[Flags]
public enum BarrierDirection : int
{
BarrierPositiveX = 1 << 0,
BarrierPositiveY = 1 << 1,
BarrierNegativeX = 1 << 2,
BarrierNegativeY = 1 << 3,
}
[DllImport(XFIXES)]
public static extern IntPtr XFixesCreatePointerBarrier(IntPtr display, IntPtr win, int x1, int y1, int x2, int y2, BarrierDirection directions, int num_deivces, IntPtr devices);
[DllImport(XFIXES)]
public static extern void XFixesDestroyPointerBarrier(IntPtr display, IntPtr b);
}
}