diff --git a/src/BizHawk.Bizware.Input/KeyMouseInput/X11KeyMouseInput.cs b/src/BizHawk.Bizware.Input/KeyMouseInput/X11KeyMouseInput.cs index 0faca9ed58..f0424a65dc 100644 --- a/src/BizHawk.Bizware.Input/KeyMouseInput/X11KeyMouseInput.cs +++ b/src/BizHawk.Bizware.Input/KeyMouseInput/X11KeyMouseInput.cs @@ -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 maskBuf = stackalloc byte[((int)XIEvents.XI_LASTEVENT + 7) / 8]; + maskBuf.Clear(); + XISetMask(maskBuf, (int)XIEvents.XI_RawMotion); + unsafe { - Span 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); } } } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index c889fd886d..c1716a1888 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -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 } } } diff --git a/src/BizHawk.Client.EmuHawk/Program.cs b/src/BizHawk.Client.EmuHawk/Program.cs index ce5706f24f..0d615b5ad6 100644 --- a/src/BizHawk.Client.EmuHawk/Program.cs +++ b/src/BizHawk.Client.EmuHawk/Program.cs @@ -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; diff --git a/src/BizHawk.Common/LSB/XFixesImports.cs b/src/BizHawk.Common/LSB/XFixesImports.cs new file mode 100644 index 0000000000..de7e7c8678 --- /dev/null +++ b/src/BizHawk.Common/LSB/XFixesImports.cs @@ -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); + } +}