diff --git a/src/BizHawk.Bizware.Input/BizHawk.Bizware.Input.csproj b/src/BizHawk.Bizware.Input/BizHawk.Bizware.Input.csproj index ffb3ee4b93..fe910a5fe1 100644 --- a/src/BizHawk.Bizware.Input/BizHawk.Bizware.Input.csproj +++ b/src/BizHawk.Bizware.Input/BizHawk.Bizware.Input.csproj @@ -9,6 +9,6 @@ - + diff --git a/src/BizHawk.Bizware.Input/HostInputFocus.cs b/src/BizHawk.Bizware.Input/HostInputFocus.cs new file mode 100644 index 0000000000..57a8bff423 --- /dev/null +++ b/src/BizHawk.Bizware.Input/HostInputFocus.cs @@ -0,0 +1,13 @@ +#nullable enable + +namespace BizHawk.Bizware.Input +{ + [Flags] + public enum HostInputFocus + { + None = 0, + Mouse = 1, + Keyboard = 2, + Pad = 4 + } +} diff --git a/src/BizHawk.Client.Common/input/HostInputAdapter.cs b/src/BizHawk.Bizware.Input/IHostInputAdapter.cs similarity index 76% rename from src/BizHawk.Client.Common/input/HostInputAdapter.cs rename to src/BizHawk.Bizware.Input/IHostInputAdapter.cs index 4198544a25..8947a7ed02 100644 --- a/src/BizHawk.Client.Common/input/HostInputAdapter.cs +++ b/src/BizHawk.Bizware.Input/IHostInputAdapter.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; -namespace BizHawk.Client.Common +namespace BizHawk.Bizware.Input { /// this was easier than trying to make static classes instantiable... /// TODO: Reconsider if we want to hand over the main form handle @@ -18,17 +18,17 @@ namespace BizHawk.Client.Common /// keys are pad prefixes "X# "/"J# " (with the trailing space) IReadOnlyDictionary> GetHapticsChannels(); - void ReInitGamepads(IntPtr mainFormHandle); - void PreprocessHostGamepads(); - void ProcessHostGamepads(Action handleButton, Action handleAxis); + void ProcessHostGamepads(Action handleButton, Action handleAxis); IEnumerable ProcessHostKeyboards(); + (int DeltaX, int DeltaY) ProcessHostMice(); + /// implementors may store this for use during the next call void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot); - void UpdateConfig(Config config); + void SetAlternateKeyboardLayoutEnableCallback(Func getHandleAlternateKeyboardLayouts); } } diff --git a/src/BizHawk.Bizware.Input/IPC/IPCKeyInput.cs b/src/BizHawk.Bizware.Input/IPC/IPCKeyInput.cs index f8b5d3a14b..7d51686f0c 100644 --- a/src/BizHawk.Bizware.Input/IPC/IPCKeyInput.cs +++ b/src/BizHawk.Bizware.Input/IPC/IPCKeyInput.cs @@ -4,8 +4,6 @@ using System.IO; using System.IO.Pipes; using System.Threading; -using BizHawk.Client.Common; - // this is not a very safe or pretty protocol, I'm not proud of it namespace BizHawk.Bizware.Input { @@ -21,7 +19,7 @@ namespace BizHawk.Bizware.Input } } - private static readonly List PendingKeyEvents = new(); + private static readonly List PendingKeyEvents = [ ]; private static bool IPCActive; private static void IPCThread() diff --git a/src/BizHawk.Bizware.Input/KeyInput/IKeyInput.cs b/src/BizHawk.Bizware.Input/KeyInput/IKeyInput.cs deleted file mode 100644 index b261394adf..0000000000 --- a/src/BizHawk.Bizware.Input/KeyInput/IKeyInput.cs +++ /dev/null @@ -1,13 +0,0 @@ -#nullable enable - -using System.Collections.Generic; - -using BizHawk.Client.Common; - -namespace BizHawk.Bizware.Input -{ - internal interface IKeyInput : IDisposable - { - IEnumerable Update(bool handleAltKbLayouts); - } -} diff --git a/src/BizHawk.Bizware.Input/KeyInput/KeyInputFactory.cs b/src/BizHawk.Bizware.Input/KeyInput/KeyInputFactory.cs deleted file mode 100644 index 881f7ae201..0000000000 --- a/src/BizHawk.Bizware.Input/KeyInput/KeyInputFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ -#nullable enable - -using BizHawk.Common; - -namespace BizHawk.Bizware.Input -{ - internal static class KeyInputFactory - { - public static IKeyInput CreateKeyInput() => OSTailoredCode.CurrentOS switch - { - OSTailoredCode.DistinctOS.Linux => new X11KeyInput(), - OSTailoredCode.DistinctOS.macOS => new QuartzKeyInput(), - OSTailoredCode.DistinctOS.Windows => new RawKeyInput(), - _ => throw new InvalidOperationException("Unknown OS"), - }; - } -} diff --git a/src/BizHawk.Client.Common/input/DistinctKey.cs b/src/BizHawk.Bizware.Input/KeyMouseInput/DistinctKey.cs similarity index 99% rename from src/BizHawk.Client.Common/input/DistinctKey.cs rename to src/BizHawk.Bizware.Input/KeyMouseInput/DistinctKey.cs index 6b6dc1f6d4..363bd8dc1c 100644 --- a/src/BizHawk.Client.Common/input/DistinctKey.cs +++ b/src/BizHawk.Bizware.Input/KeyMouseInput/DistinctKey.cs @@ -3,7 +3,7 @@ // ReSharper disable CommentTypo // ReSharper disable IdentifierTypo // ReSharper disable UnusedMember.Global -namespace BizHawk.Client.Common +namespace BizHawk.Bizware.Input { /// values are one-to-one with System.Windows.Input.Key except and which were added for this project /// copied from MIT-licensed WPF source: https://github.com/dotnet/wpf/blob/49bb41ad83abeb5ae22e4c59d0f43c1287acac00/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Input/Key.cs diff --git a/src/BizHawk.Bizware.Input/KeyMouseInput/IKeyMouseInput.cs b/src/BizHawk.Bizware.Input/KeyMouseInput/IKeyMouseInput.cs new file mode 100644 index 0000000000..de4151fd70 --- /dev/null +++ b/src/BizHawk.Bizware.Input/KeyMouseInput/IKeyMouseInput.cs @@ -0,0 +1,13 @@ +#nullable enable + +using System.Collections.Generic; + +namespace BizHawk.Bizware.Input +{ + internal interface IKeyMouseInput : IDisposable + { + IEnumerable UpdateKeyInputs(bool handleAltKbLayouts); + + (int DeltaX, int DeltaY) UpdateMouseInput(); + } +} diff --git a/src/BizHawk.Bizware.Input/KeyMouseInput/KeyEvent.cs b/src/BizHawk.Bizware.Input/KeyMouseInput/KeyEvent.cs new file mode 100644 index 0000000000..36475fec3c --- /dev/null +++ b/src/BizHawk.Bizware.Input/KeyMouseInput/KeyEvent.cs @@ -0,0 +1,6 @@ +#nullable enable + +namespace BizHawk.Bizware.Input +{ + public readonly record struct KeyEvent(DistinctKey Key, bool Pressed); +} diff --git a/src/BizHawk.Bizware.Input/KeyMouseInput/KeyMouseInputFactory.cs b/src/BizHawk.Bizware.Input/KeyMouseInput/KeyMouseInputFactory.cs new file mode 100644 index 0000000000..c7739d8749 --- /dev/null +++ b/src/BizHawk.Bizware.Input/KeyMouseInput/KeyMouseInputFactory.cs @@ -0,0 +1,17 @@ +#nullable enable + +using BizHawk.Common; + +namespace BizHawk.Bizware.Input +{ + internal static class KeyMouseInputFactory + { + public static IKeyMouseInput CreateKeyMouseInput() => OSTailoredCode.CurrentOS switch + { + OSTailoredCode.DistinctOS.Linux => new X11KeyMouseInput(), + OSTailoredCode.DistinctOS.macOS => new QuartzKeyMouseInput(), + OSTailoredCode.DistinctOS.Windows => new RawKeyMouseInput(), + _ => throw new InvalidOperationException("Unknown OS"), + }; + } +} diff --git a/src/BizHawk.Bizware.Input/KeyInput/QuartzKeyInput.cs b/src/BizHawk.Bizware.Input/KeyMouseInput/QuartzKeyMouseInput.cs similarity index 91% rename from src/BizHawk.Bizware.Input/KeyInput/QuartzKeyInput.cs rename to src/BizHawk.Bizware.Input/KeyMouseInput/QuartzKeyMouseInput.cs index cb7191eb6d..88313d64c0 100644 --- a/src/BizHawk.Bizware.Input/KeyInput/QuartzKeyInput.cs +++ b/src/BizHawk.Bizware.Input/KeyMouseInput/QuartzKeyMouseInput.cs @@ -2,13 +2,11 @@ using System.Collections.Generic; -using BizHawk.Client.Common; - using static BizHawk.Common.QuartzImports; namespace BizHawk.Bizware.Input { - internal sealed class QuartzKeyInput : IKeyInput + internal sealed class QuartzKeyMouseInput : IKeyMouseInput { private readonly bool[] LastKeyState = new bool[0x7F]; @@ -16,7 +14,7 @@ namespace BizHawk.Bizware.Input { } - public IEnumerable Update(bool handleAltKbLayouts) + public IEnumerable UpdateKeyInputs(bool handleAltKbLayouts) { var keyEvents = new List(); for (var keycode = 0; keycode < 0x7F; keycode++) @@ -28,7 +26,7 @@ namespace BizHawk.Bizware.Input { if (KeyEnumMap.TryGetValue((CGKeyCode)keycode, out var key)) { - keyEvents.Add(new(key, pressed: keystate)); + keyEvents.Add(new(key, Pressed: keystate)); LastKeyState[keycode] = keystate; } } @@ -37,6 +35,13 @@ namespace BizHawk.Bizware.Input return keyEvents; } + public (int DeltaX, int DeltaY) UpdateMouseInput() + { + // probably wrong, need to recheck when we actually get macos support + CGGetLastMouseDelta(out var deltaX, out var deltaY); + return (deltaX, deltaY); + } + private static readonly IReadOnlyDictionary KeyEnumMap = new Dictionary { [CGKeyCode.kVK_ANSI_A] = DistinctKey.A, diff --git a/src/BizHawk.Bizware.Input/KeyInput/RawKeyInput.cs b/src/BizHawk.Bizware.Input/KeyMouseInput/RawKeyMouseInput.cs similarity index 82% rename from src/BizHawk.Bizware.Input/KeyInput/RawKeyInput.cs rename to src/BizHawk.Bizware.Input/KeyMouseInput/RawKeyMouseInput.cs index 175f782b21..3968cbc45b 100644 --- a/src/BizHawk.Bizware.Input/KeyInput/RawKeyInput.cs +++ b/src/BizHawk.Bizware.Input/KeyMouseInput/RawKeyMouseInput.cs @@ -1,9 +1,9 @@ #nullable enable using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using BizHawk.Client.Common; using BizHawk.Common; using BizHawk.Common.CollectionExtensions; @@ -13,10 +13,10 @@ using static BizHawk.Common.WmImports; namespace BizHawk.Bizware.Input { /// - /// Note: Only 1 window per device class (i.e. keyboards) is actually allowed to RAWINPUT (last one to call RegisterRawInputDevices) + /// Note: Only 1 window per device class (e.g. keyboards) is actually allowed to use RAWINPUT (last one to call RegisterRawInputDevices) /// So only one instance can actually be used at the same time /// - internal sealed class RawKeyInput : IKeyInput + internal sealed class RawKeyMouseInput : IKeyMouseInput { private const int WM_CLOSE = 0x0010; private const int WM_INPUT = 0x00FF; @@ -24,6 +24,8 @@ namespace BizHawk.Bizware.Input private IntPtr RawInputWindow; private bool _handleAltKbLayouts; private List _keyEvents = [ ]; + private (int X, int Y) _mouseDelta; + private (int X, int Y) _lastMouseAbsPos; private readonly object _lockObj = new(); private bool _disposed; @@ -38,7 +40,7 @@ namespace BizHawk.Bizware.Input var wc = default(WNDCLASSW); wc.lpfnWndProc = _wndProc; wc.hInstance = LoaderApiImports.GetModuleHandleW(null); - wc.lpszClassName = "RawKeyInputClass"; + wc.lpszClassName = "RawKeyMouseInputClass"; var atom = RegisterClassW(ref wc); if (atom == IntPtr.Zero) @@ -58,15 +60,15 @@ namespace BizHawk.Bizware.Input } GCHandle handle; - RawKeyInput rawKeyInput; + RawKeyMouseInput rawKeyMouseInput; if (uMsg != WM_INPUT) { if (uMsg == WM_CLOSE) { SetWindowLongPtrW(hWnd, GWLP_USERDATA, IntPtr.Zero); handle = GCHandle.FromIntPtr(ud); - rawKeyInput = (RawKeyInput)handle.Target; - Marshal.FreeCoTaskMem(rawKeyInput.RawInputBuffer); + rawKeyMouseInput = (RawKeyMouseInput)handle.Target; + Marshal.FreeCoTaskMem(rawKeyMouseInput.RawInputBuffer); handle.Free(); } @@ -86,7 +88,7 @@ namespace BizHawk.Bizware.Input : stackalloc IntPtr[(size + sizeof(IntPtr) - 1) / sizeof(IntPtr)]; handle = GCHandle.FromIntPtr(ud); - rawKeyInput = (RawKeyInput)handle.Target; + rawKeyMouseInput = (RawKeyMouseInput)handle.Target; fixed (IntPtr* p = buffer) { @@ -99,14 +101,19 @@ namespace BizHawk.Bizware.Input if (input->header.dwType == RAWINPUTHEADER.RIM_TYPE.KEYBOARD) { - rawKeyInput.AddKeyInput(&input->data.keyboard); + rawKeyMouseInput.AddKeyInput(&input->data.keyboard); + } + + if (input->header.dwType == RAWINPUTHEADER.RIM_TYPE.MOUSE) + { + rawKeyMouseInput.AddMouseInput(&input->data.mouse); } } while (true) { - var rawInputBuffer = (RAWINPUT*)rawKeyInput.RawInputBuffer; - size = rawKeyInput.RawInputBufferSize; + var rawInputBuffer = (RAWINPUT*)rawKeyMouseInput.RawInputBuffer; + size = rawKeyMouseInput.RawInputBufferSize; var count = GetRawInputBuffer(rawInputBuffer, ref size, sizeof(RAWINPUTHEADER)); if (count == 0) { @@ -121,8 +128,8 @@ namespace BizHawk.Bizware.Input const int ERROR_INSUFFICIENT_BUFFER = 0x7A; if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER) { - rawKeyInput.RawInputBufferSize *= 2; - rawKeyInput.RawInputBuffer = Marshal.ReAllocCoTaskMem(rawKeyInput.RawInputBuffer, rawKeyInput.RawInputBufferSize); + rawKeyMouseInput.RawInputBufferSize *= 2; + rawKeyMouseInput.RawInputBuffer = Marshal.ReAllocCoTaskMem(rawKeyMouseInput.RawInputBuffer, rawKeyMouseInput.RawInputBufferSize); continue; } @@ -133,8 +140,14 @@ namespace BizHawk.Bizware.Input { if (rawInputBuffer->header.dwType == RAWINPUTHEADER.RIM_TYPE.KEYBOARD) { - var keyboard = (RAWKEYBOARD*)((byte*)&rawInputBuffer->data.keyboard + rawKeyInput.RawInputBufferDataOffset); - rawKeyInput.AddKeyInput(keyboard); + var keyboard = (RAWKEYBOARD*)((byte*)&rawInputBuffer->data.keyboard + rawKeyMouseInput.RawInputBufferDataOffset); + rawKeyMouseInput.AddKeyInput(keyboard); + } + + if (rawInputBuffer->header.dwType == RAWINPUTHEADER.RIM_TYPE.MOUSE) + { + var mouse = (RAWMOUSE*)((byte*)&rawInputBuffer->data.mouse + rawKeyMouseInput.RawInputBufferDataOffset); + rawKeyMouseInput.AddMouseInput(mouse); } var packetSize = rawInputBuffer->header.dwSize; @@ -174,6 +187,22 @@ namespace BizHawk.Bizware.Input } } + private unsafe void AddMouseInput(RAWMOUSE* mouse) + { + // raw input usually doesn't report absolute inputs, only in odd cases with e.g. touchscreen and rdp screen + if ((mouse->usFlags & RAWMOUSE.MOUSE_FLAGS.MOVE_ABSOLUTE) == RAWMOUSE.MOUSE_FLAGS.MOVE_ABSOLUTE) + { + _mouseDelta.X += mouse->lLastX - _lastMouseAbsPos.X; + _mouseDelta.Y += mouse->lLastY - _lastMouseAbsPos.Y; + _lastMouseAbsPos = (mouse->lLastX, mouse->lLastY); + } + else // ((mouse->usFlags & RAWMOUSE.MOUSE_FLAGS.MOVE_ABSOLUTE) == RAWMOUSE.MOUSE_FLAGS.MOVE_RELATIVE) + { + _mouseDelta.X += mouse->lLastX; + _mouseDelta.Y += mouse->lLastY; + } + } + private static IntPtr CreateRawInputWindow() { const int WS_CHILD = 0x40000000; @@ -196,22 +225,29 @@ namespace BizHawk.Bizware.Input throw new InvalidOperationException("Failed to create RAWINPUT window"); } - var rid = default(RAWINPUTDEVICE); - rid.usUsagePage = RAWINPUTDEVICE.HidUsagePage.GENERIC; - rid.usUsage = RAWINPUTDEVICE.HidUsageId.GENERIC_KEYBOARD; - rid.dwFlags = RAWINPUTDEVICE.RIDEV.INPUTSINK; - rid.hwndTarget = window; - - if (!RegisterRawInputDevices(ref rid, 1, Marshal.SizeOf())) + unsafe { - DestroyWindow(window); - throw new InvalidOperationException("Failed to register RAWINPUTDEVICE"); + var rid = stackalloc RAWINPUTDEVICE[2]; + rid[0].usUsagePage = RAWINPUTDEVICE.HidUsagePage.GENERIC; + rid[0].usUsage = RAWINPUTDEVICE.HidUsageId.GENERIC_KEYBOARD; + rid[0].dwFlags = RAWINPUTDEVICE.RIDEV.INPUTSINK; + rid[0].hwndTarget = window; + rid[1].usUsagePage = RAWINPUTDEVICE.HidUsagePage.GENERIC; + rid[1].usUsage = RAWINPUTDEVICE.HidUsageId.GENERIC_MOUSE; + rid[1].dwFlags = RAWINPUTDEVICE.RIDEV.INPUTSINK; + rid[1].hwndTarget = window; + + if (!RegisterRawInputDevices(rid, 2, sizeof(RAWINPUTDEVICE))) + { + DestroyWindow(window); + throw new InvalidOperationException("Failed to register RAWINPUTDEVICE"); + } } return window; } - public RawKeyInput() + public RawKeyMouseInput() { if (OSTailoredCode.IsUnixHost) { @@ -236,7 +272,7 @@ namespace BizHawk.Bizware.Input RawInputBufferDataOffset = isWow64 ? 8 : 0; } - RawInputBufferSize = (Marshal.SizeOf() + RawInputBufferDataOffset) * 16; + RawInputBufferSize = (Unsafe.SizeOf() + RawInputBufferDataOffset) * 16; RawInputBuffer = Marshal.AllocCoTaskMem(RawInputBufferSize); } @@ -261,7 +297,7 @@ namespace BizHawk.Bizware.Input } } - public IEnumerable Update(bool handleAltKbLayouts) + public IEnumerable UpdateKeyInputs(bool handleAltKbLayouts) { lock (_lockObj) { @@ -291,6 +327,34 @@ namespace BizHawk.Bizware.Input } } + public (int DeltaX, int DeltaY) UpdateMouseInput() + { + lock (_lockObj) + { + if (_disposed) + { + return default; + } + + if (RawInputWindow == IntPtr.Zero) + { + RawInputWindow = CreateRawInputWindow(); + var handle = GCHandle.Alloc(this, GCHandleType.Normal); + SetWindowLongPtrW(RawInputWindow, GWLP_USERDATA, GCHandle.ToIntPtr(handle)); + } + + while (PeekMessageW(out var msg, RawInputWindow, 0, 0, PM_REMOVE)) + { + TranslateMessage(ref msg); + DispatchMessageW(ref msg); + } + + var ret = _mouseDelta; + _mouseDelta = default; + return ret; + } + } + private static readonly RawKey[] _rawKeysNoTranslation = [ RawKey.NUMPAD0, diff --git a/src/BizHawk.Bizware.Input/KeyInput/X11KeyInput.cs b/src/BizHawk.Bizware.Input/KeyMouseInput/X11KeyMouseInput.cs similarity index 74% rename from src/BizHawk.Bizware.Input/KeyInput/X11KeyInput.cs rename to src/BizHawk.Bizware.Input/KeyMouseInput/X11KeyMouseInput.cs index a57f33f1e9..f7667eae73 100644 --- a/src/BizHawk.Bizware.Input/KeyInput/X11KeyInput.cs +++ b/src/BizHawk.Bizware.Input/KeyMouseInput/X11KeyMouseInput.cs @@ -1,26 +1,28 @@ #nullable enable using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; -using BizHawk.Client.Common; + using BizHawk.Common; using BizHawk.Common.CollectionExtensions; +using static BizHawk.Common.XInput2Imports; using static BizHawk.Common.XlibImports; // a lot of this code is taken from OpenTK namespace BizHawk.Bizware.Input { - internal sealed class X11KeyInput : IKeyInput + internal sealed class X11KeyMouseInput : IKeyMouseInput { private IntPtr Display; private readonly bool[] LastKeyState = new bool[256]; private readonly object LockObj = new(); private readonly DistinctKey[] KeyEnumMap = new DistinctKey[256]; + private readonly bool _supportsXInput2; + private readonly int _xi2Opcode; - public X11KeyInput() + public X11KeyMouseInput() { if (OSTailoredCode.CurrentOS != OSTailoredCode.DistinctOS.Linux) { @@ -32,24 +34,58 @@ namespace BizHawk.Bizware.Input if (Display == IntPtr.Zero) { // There doesn't seem to be a convention for what exception type to throw in these situations. Can't use NRE. Well... -// _ = Unsafe.AsRef()!; // hmm + // _ = Unsafe.AsRef()!; // hmm // InvalidOperationException doesn't match. Exception it is. --yoshi throw new Exception("Could not open XDisplay"); } - using (new XLock(Display)) - { - // check if we can use XKb - int major = 1, minor = 0; - var supportsXkb = XkbQueryExtension(Display, out _, out _, out _, ref major, ref minor); + // check if we can use XKb + int major = 1, minor = 0; + var supportsXkb = XkbQueryExtension(Display, out _, out _, out _, ref major, ref minor); - if (supportsXkb) + if (supportsXkb) + { + // we generally want this behavior + XkbSetDetectableAutoRepeat(Display, true, out _); + } + + CreateKeyMap(supportsXkb); + + _supportsXInput2 = XQueryExtension(Display, "XInputExtension", out _xi2Opcode, out _, out _); + if (_supportsXInput2) + { + try { - // we generally want this behavior - XkbSetDetectableAutoRepeat(Display, true, out _); + (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 + _supportsXInput2 = false; } - CreateKeyMap(supportsXkb); + if (_supportsXInput2) + { + Span maskBuf = stackalloc byte[((int)XIEvents.XI_LASTEVENT + 7) / 8]; + maskBuf.Clear(); + XISetMask(maskBuf, (int)XIEvents.XI_RawMotion); + unsafe + { + 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); + } + } + } } } @@ -65,23 +101,19 @@ namespace BizHawk.Bizware.Input } } - public unsafe IEnumerable Update(bool handleAltKbLayouts) + public unsafe IEnumerable UpdateKeyInputs(bool handleAltKbLayouts) { lock (LockObj) { // Can't update without a display connection if (Display == IntPtr.Zero) { - return Enumerable.Empty(); + return [ ]; } var keys = stackalloc byte[32]; - - using (new XLock(Display)) - { - // this apparently always returns 1 no matter what? - _ = XQueryKeymap(Display, keys); - } + // this apparently always returns 1 no matter what? + _ = XQueryKeymap(Display, keys); var keyEvents = new List(); for (var keycode = 0; keycode < 256; keycode++) @@ -92,7 +124,7 @@ namespace BizHawk.Bizware.Input var keystate = (keys[keycode >> 3] >> (keycode & 0x07) & 0x01) != 0; if (LastKeyState[keycode] != keystate) { - keyEvents.Add(new(key, pressed: keystate)); + keyEvents.Add(new(key, Pressed: keystate)); LastKeyState[keycode] = keystate; } } @@ -102,6 +134,86 @@ namespace BizHawk.Bizware.Input } } + public (int DeltaX, int DeltaY) UpdateMouseInput() + { + lock (LockObj) + { + // Can't update without a display connection + if (Display == IntPtr.Zero) + { + return default; + } + + // need XInput2 support for this + if (!_supportsXInput2) + { + return default; + } + + (double mouseDeltaX, double mouseDeltaY) = (0, 0); + while (XPending(Display) > 0) + { + _ = XNextEvent(Display, out var evt); + if (evt.xcookie.type != XEventTypes.GenericEvent + || evt.xcookie.extension != _xi2Opcode) + { + continue; + } + + if (!XGetEventData(Display, ref evt.xcookie)) + { + continue; + } + + if ((XIEvents)evt.xcookie.evtype == XIEvents.XI_RawMotion) + { + unsafe + { + var xiRawEvent = (XIRawEvent*)evt.xcookie.data; + var valuatorsMask = new Span(xiRawEvent->valuators.mask, xiRawEvent->valuators.mask_len); + if (!valuatorsMask.IsEmpty) + { + var rawValueIndex = 0; + + // not implemented until netcore / netstandard2.1 + // copied from modern runtime + static bool IsNormal(double d) + { + var bits = BitConverter.DoubleToInt64Bits(d); + bits &= 0x7FFFFFFFFFFFFFFF; + return (bits < 0x7FF0000000000000) && (bits != 0) && ((bits & 0x7FF0000000000000) == 0); + } + + if (XIMaskIsSet(valuatorsMask, 0)) + { + var deltaX = xiRawEvent->raw_values[rawValueIndex]; + if (IsNormal(deltaX)) + { + mouseDeltaX += deltaX; + } + + rawValueIndex++; + } + + if (XIMaskIsSet(valuatorsMask, 1)) + { + var deltaY = xiRawEvent->raw_values[rawValueIndex]; + if (IsNormal(deltaY)) + { + mouseDeltaY += deltaY; + } + } + } + } + } + + _ = XFreeEventData(Display, ref evt.xcookie); + } + + return ((int)mouseDeltaX, (int)mouseDeltaY); + } + } + private unsafe void CreateKeyMap(bool supportsXkb) { for (var i = 0; i < KeyEnumMap.Length; i++) diff --git a/src/BizHawk.Bizware.Input/OSTailoredKeyInputAdapter.cs b/src/BizHawk.Bizware.Input/OSTailoredKeyInputAdapter.cs index 33a601f592..b9fa23ae79 100644 --- a/src/BizHawk.Bizware.Input/OSTailoredKeyInputAdapter.cs +++ b/src/BizHawk.Bizware.Input/OSTailoredKeyInputAdapter.cs @@ -3,54 +3,53 @@ using System.Collections.Generic; using System.Linq; -using BizHawk.Client.Common; - namespace BizHawk.Bizware.Input { /// - /// Abstract class which only handles keyboard input + /// Abstract class which only handles keyboard and mouse input /// Uses OS specific functionality, as there is no good cross platform way to do this /// (Mostly as all the available cross-platform options require a focused window, arg!) /// TODO: Doesn't work for Wayland yet (must use XWayland, which Wayland users need to use anyways for BizHawk) /// - public abstract class OSTailoredKeyInputAdapter : IHostInputAdapter + public abstract class OSTailoredKeyMouseInputAdapter : IHostInputAdapter { - private IKeyInput? _keyInput; - protected Config? _config; + private IKeyMouseInput? _keyMouseInput; + protected Func? _getHandleAlternateKeyboardLayouts; public abstract string Desc { get; } public virtual void DeInitAll() - => _keyInput!.Dispose(); + => _keyMouseInput!.Dispose(); public virtual void FirstInitAll(IntPtr mainFormHandle) { - _keyInput = KeyInputFactory.CreateKeyInput(); + _keyMouseInput = KeyMouseInputFactory.CreateKeyMouseInput(); IPCKeyInput.Initialize(); // why not? this isn't necessarily OS specific } public abstract IReadOnlyDictionary> GetHapticsChannels(); - public abstract void ReInitGamepads(IntPtr mainFormHandle); - public abstract void PreprocessHostGamepads(); - public abstract void ProcessHostGamepads(Action handleButton, Action handleAxis); + public abstract void ProcessHostGamepads(Action handleButton, Action handleAxis); public virtual IEnumerable ProcessHostKeyboards() { - if (_config is null) + if (_getHandleAlternateKeyboardLayouts is null) { - throw new InvalidOperationException(nameof(ProcessHostKeyboards) + " called before the global config was passed"); + throw new InvalidOperationException(nameof(ProcessHostKeyboards) + " called before alternate keyboard layout enable callback was set"); } - var ret = _keyInput!.Update(_config.HandleAlternateKeyboardLayouts); + var ret = _keyMouseInput!.UpdateKeyInputs(_getHandleAlternateKeyboardLayouts()); return ret.Concat(IPCKeyInput.Update()); } + public virtual (int DeltaX, int DeltaY) ProcessHostMice() + => _keyMouseInput!.UpdateMouseInput(); + public abstract void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot); - public virtual void UpdateConfig(Config config) - => _config = config; + public virtual void SetAlternateKeyboardLayoutEnableCallback(Func getHandleAlternateKeyboardLayouts) + => _getHandleAlternateKeyboardLayouts = getHandleAlternateKeyboardLayouts; } } diff --git a/src/BizHawk.Bizware.Input/SDL2/SDL2InputAdapter.cs b/src/BizHawk.Bizware.Input/SDL2/SDL2InputAdapter.cs index 5697a234d5..06a7bbfc6f 100644 --- a/src/BizHawk.Bizware.Input/SDL2/SDL2InputAdapter.cs +++ b/src/BizHawk.Bizware.Input/SDL2/SDL2InputAdapter.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using BizHawk.Client.Common; using BizHawk.Common; using BizHawk.Common.CollectionExtensions; #if BIZHAWKBUILD_DEBUG_RUMBLE @@ -16,9 +15,9 @@ using static SDL2.SDL; namespace BizHawk.Bizware.Input { - public sealed class SDL2InputAdapter : OSTailoredKeyInputAdapter + public sealed class SDL2InputAdapter : OSTailoredKeyMouseInputAdapter { - private static readonly IReadOnlyCollection SDL2_HAPTIC_CHANNEL_NAMES = new[] { "Left", "Right" }; + private static readonly IReadOnlyCollection SDL2_HAPTIC_CHANNEL_NAMES = [ "Left", "Right" ]; private IReadOnlyDictionary _lastHapticsSnapshot = new Dictionary(); @@ -130,11 +129,7 @@ namespace BizHawk.Bizware.Input .Where(pad => pad.HasRumble) .Select(pad => pad.InputNamePrefix) .ToDictionary(s => s, _ => SDL2_HAPTIC_CHANNEL_NAMES) - : new(); - } - - public override void ReInitGamepads(IntPtr mainFormHandle) - { + : [ ]; } public override void PreprocessHostGamepads() @@ -153,15 +148,15 @@ namespace BizHawk.Bizware.Input DoSDLEventLoop(); } - public override void ProcessHostGamepads(Action handleButton, Action handleAxis) + public override void ProcessHostGamepads(Action handleButton, Action handleAxis) { if (!_isInit) return; foreach (var pad in SDL2Gamepad.EnumerateDevices()) { - foreach (var but in pad.ButtonGetters) + foreach (var (ButtonName, GetIsPressed) in pad.ButtonGetters) { - handleButton(pad.InputNamePrefix + but.ButtonName, but.GetIsPressed(), ClientInputFocus.Pad); + handleButton(pad.InputNamePrefix + ButtonName, GetIsPressed(), HostInputFocus.Pad); } foreach (var (axisID, f) in pad.GetAxes()) @@ -188,7 +183,7 @@ namespace BizHawk.Bizware.Input { return _isInit ? base.ProcessHostKeyboards() - : Enumerable.Empty(); + : [ ]; } public override void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot) diff --git a/src/BizHawk.Client.Common/BizHawk.Client.Common.csproj b/src/BizHawk.Client.Common/BizHawk.Client.Common.csproj index 8a878ebfde..4eacc8bd3a 100644 --- a/src/BizHawk.Client.Common/BizHawk.Client.Common.csproj +++ b/src/BizHawk.Client.Common/BizHawk.Client.Common.csproj @@ -16,6 +16,7 @@ + diff --git a/src/BizHawk.Client.Common/input/DistinctKeyNameOverrides.cs b/src/BizHawk.Client.Common/input/DistinctKeyNameOverrides.cs index ce04224e53..da85486454 100644 --- a/src/BizHawk.Client.Common/input/DistinctKeyNameOverrides.cs +++ b/src/BizHawk.Client.Common/input/DistinctKeyNameOverrides.cs @@ -1,8 +1,10 @@ +using BizHawk.Bizware.Input; + namespace BizHawk.Client.Common { public static class DistinctKeyNameOverrides { - public static string GetName(in DistinctKey k) + public static string GetName(DistinctKey k) => k switch { DistinctKey.Back => "Backspace", diff --git a/src/BizHawk.Client.Common/input/InputEvent.cs b/src/BizHawk.Client.Common/input/InputEvent.cs index bfb2017f92..fcbd07e07f 100644 --- a/src/BizHawk.Client.Common/input/InputEvent.cs +++ b/src/BizHawk.Client.Common/input/InputEvent.cs @@ -3,24 +3,17 @@ using System.Collections.Generic; using System.Text; +using BizHawk.Bizware.Input; + namespace BizHawk.Client.Common { - [Flags] - public enum ClientInputFocus - { - None = 0, - Mouse = 1, - Keyboard = 2, - Pad = 4 - } - public class InputEvent { public InputEventType EventType; public LogicalButton LogicalButton; - public ClientInputFocus Source; + public HostInputFocus Source; public override string ToString() => $"{EventType}:{LogicalButton}"; } diff --git a/src/BizHawk.Client.Common/input/KeyEvent.cs b/src/BizHawk.Client.Common/input/KeyEvent.cs deleted file mode 100644 index 89feec15a8..0000000000 --- a/src/BizHawk.Client.Common/input/KeyEvent.cs +++ /dev/null @@ -1,17 +0,0 @@ -#nullable enable - -namespace BizHawk.Client.Common -{ - public readonly struct KeyEvent - { - public readonly DistinctKey Key; - - public readonly bool Pressed; - - public KeyEvent(DistinctKey key, bool pressed) - { - Key = key; - Pressed = pressed; - } - } -} diff --git a/src/BizHawk.Client.EmuHawk/Input/Input.cs b/src/BizHawk.Client.EmuHawk/Input/Input.cs index 8dfa91707b..e11770f939 100644 --- a/src/BizHawk.Client.EmuHawk/Input/Input.cs +++ b/src/BizHawk.Client.EmuHawk/Input/Input.cs @@ -18,10 +18,10 @@ namespace BizHawk.Client.EmuHawk /// Why is this receiving a control, but actually using it as a Form (where the WantingMouseFocus is checked?) /// Because later we might change it to work off the control, specifically, if a control is supplied (normally actually a Form will be supplied) /// - public void ControlInputFocus(Control c, ClientInputFocus types, bool wants) + public void ControlInputFocus(Control c, HostInputFocus types, bool wants) { - if (types.HasFlag(ClientInputFocus.Mouse) && wants) _wantingMouseFocus.Add(c); - if (types.HasFlag(ClientInputFocus.Mouse) && !wants) _wantingMouseFocus.Remove(c); + if (types.HasFlag(HostInputFocus.Mouse) && wants) _wantingMouseFocus.Add(c); + if (types.HasFlag(HostInputFocus.Mouse) && !wants) _wantingMouseFocus.Remove(c); } private readonly HashSet _wantingMouseFocus = new HashSet(); @@ -48,7 +48,7 @@ namespace BizHawk.Client.EmuHawk Adapter = new SDL2InputAdapter(); Console.WriteLine($"Using {Adapter.Desc} for host input (keyboard + gamepads)"); - Adapter.UpdateConfig(_currentConfig); + Adapter.SetAlternateKeyboardLayoutEnableCallback(() => _currentConfig.HandleAlternateKeyboardLayouts); Adapter.FirstInitAll(mainFormHandle); _updateThread = new Thread(UpdateThreadProc) { @@ -96,7 +96,7 @@ namespace BizHawk.Client.EmuHawk ["Shift"] = "LeftShift", }; - private void HandleButton(string button, bool newState, ClientInputFocus source) + private void HandleButton(string button, bool newState, HostInputFocus source) { if (!(_currentConfig.MergeLAndRModifierKeys && ModifierKeyPreMap.TryGetValue(button, out var button1))) button1 = button; var modIndex = _currentConfig.ModifierKeysEffective.IndexOf(button1); @@ -135,7 +135,7 @@ namespace BizHawk.Client.EmuHawk private void HandleAxis(string axis, int newValue) { - if (ShouldSwallow(MainFormInputAllowedCallback(false), ClientInputFocus.Pad)) + if (ShouldSwallow(MainFormInputAllowedCallback(false), HostInputFocus.Pad)) return; if (_trackDeltas) @@ -195,9 +195,9 @@ namespace BizHawk.Client.EmuHawk { _currentConfig = _getConfigCallback(); UpdateModifierKeysEffective(); - Adapter.UpdateConfig(_currentConfig); var keyEvents = Adapter.ProcessHostKeyboards(); + var (mouseDeltaX, mouseDeltaY) = Adapter.ProcessHostMice(); Adapter.PreprocessHostGamepads(); //this block is going to massively modify data structures that the binding method uses, so we have to lock it all @@ -208,7 +208,7 @@ namespace BizHawk.Client.EmuHawk //analyze keys foreach (var ke in keyEvents) { - HandleButton(DistinctKeyNameOverrides.GetName(in ke.Key), ke.Pressed, ClientInputFocus.Keyboard); + HandleButton(DistinctKeyNameOverrides.GetName(ke.Key), ke.Pressed, HostInputFocus.Keyboard); } lock (_axisValues) @@ -217,7 +217,6 @@ namespace BizHawk.Client.EmuHawk Adapter.ProcessHostGamepads(HandleButton, HandleAxis); // analyze moose - // other sorts of mouse api (raw input) could easily be added as a separate listing under a different class if (_wantingMouseFocus.Contains(Form.ActiveForm)) { var mousePos = Control.MousePosition; @@ -235,11 +234,15 @@ namespace BizHawk.Client.EmuHawk _axisValues["WMouse Y"] = mousePos.Y; var mouseBtns = Control.MouseButtons; - HandleButton("WMouse L", (mouseBtns & MouseButtons.Left) != 0, ClientInputFocus.Mouse); - HandleButton("WMouse M", (mouseBtns & MouseButtons.Middle) != 0, ClientInputFocus.Mouse); - HandleButton("WMouse R", (mouseBtns & MouseButtons.Right) != 0, ClientInputFocus.Mouse); - HandleButton("WMouse 1", (mouseBtns & MouseButtons.XButton1) != 0, ClientInputFocus.Mouse); - HandleButton("WMouse 2", (mouseBtns & MouseButtons.XButton2) != 0, ClientInputFocus.Mouse); + HandleButton("WMouse L", (mouseBtns & MouseButtons.Left) != 0, HostInputFocus.Mouse); + HandleButton("WMouse M", (mouseBtns & MouseButtons.Middle) != 0, HostInputFocus.Mouse); + HandleButton("WMouse R", (mouseBtns & MouseButtons.Right) != 0, HostInputFocus.Mouse); + HandleButton("WMouse 1", (mouseBtns & MouseButtons.XButton1) != 0, HostInputFocus.Mouse); + HandleButton("WMouse 2", (mouseBtns & MouseButtons.XButton2) != 0, HostInputFocus.Mouse); + + // raw (relative) mouse input + _axisValues["RMouse X"] = mouseDeltaX; + _axisValues["RMouse Y"] = mouseDeltaY; } else { @@ -279,9 +282,9 @@ namespace BizHawk.Client.EmuHawk } } - private static bool ShouldSwallow(AllowInput allowInput, ClientInputFocus inputFocus) + private static bool ShouldSwallow(AllowInput allowInput, HostInputFocus inputFocus) { - return allowInput == AllowInput.None || (allowInput == AllowInput.OnlyController && inputFocus != ClientInputFocus.Pad); + return allowInput == AllowInput.None || (allowInput == AllowInput.OnlyController && inputFocus != HostInputFocus.Pad); } public void StartListeningForAxisEvents() diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index c8ea9768bf..092712f143 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -15,6 +15,7 @@ using System.Security.Principal; using System.IO.Pipes; using BizHawk.Bizware.Graphics; +using BizHawk.Bizware.Input; using BizHawk.Common; using BizHawk.Common.BufferExtensions; @@ -58,9 +59,9 @@ namespace BizHawk.Client.EmuHawk private readonly ToolStripMenuItemEx NullHawkVSysSubmenu = new() { Enabled = false, Text = "—" }; private void MainForm_Load(object sender, EventArgs e) - { + { UpdateWindowTitle(); - + { for (int i = 1; i <= WINDOW_SCALE_MAX; i++) { @@ -1163,12 +1164,12 @@ namespace BizHawk.Client.EmuHawk protected override void OnActivated(EventArgs e) { base.OnActivated(e); - Input.Instance.ControlInputFocus(this, ClientInputFocus.Mouse, true); + Input.Instance.ControlInputFocus(this, HostInputFocus.Mouse, true); } protected override void OnDeactivate(EventArgs e) { - Input.Instance.ControlInputFocus(this, ClientInputFocus.Mouse, false); + Input.Instance.ControlInputFocus(this, HostInputFocus.Mouse, false); base.OnDeactivate(e); } @@ -2801,8 +2802,6 @@ namespace BizHawk.Client.EmuHawk // Alt key hacks protected override void WndProc(ref Message m) { - if (m.Msg == WmDeviceChange) Input.Instance.Adapter.ReInitGamepads(Handle); - // this is necessary to trap plain alt keypresses so that only our hotkey system gets them if (m.Msg == 0x0112) // WM_SYSCOMMAND { diff --git a/src/BizHawk.Common/LSB/XInput2Imports.cs b/src/BizHawk.Common/LSB/XInput2Imports.cs new file mode 100644 index 0000000000..85696f1f56 --- /dev/null +++ b/src/BizHawk.Common/LSB/XInput2Imports.cs @@ -0,0 +1,96 @@ +using System.Runtime.InteropServices; + +namespace BizHawk.Common +{ + public static class XInput2Imports + { + private const string XI2 = "libXi.so.6"; + + [DllImport(XI2)] + public static extern int XIQueryVersion(IntPtr display, ref int major_version_inout, ref int minor_version_inout); + + public enum XIEvents + { + XI_DeviceChanged = 1, + XI_KeyPress = 2, + XI_KeyRelease = 3, + XI_ButtonPress = 4, + XI_ButtonRelease = 5, + XI_Motion = 6, + XI_Enter = 7, + XI_Leave = 8, + XI_FocusIn = 9, + XI_FocusOut = 10, + XI_HierarchyChanged = 11, + XI_PropertyEvent = 12, + XI_RawKeyPress = 13, + XI_RawKeyRelease = 14, + XI_RawButtonPress = 15, + XI_RawButtonRelease = 16, + XI_RawMotion = 17, + XI_TouchBegin = 18, // XI 2.2 + XI_TouchUpdate = 19, + XI_TouchEnd = 20, + XI_TouchOwnership = 21, + XI_RawTouchBegin = 22, + XI_RawTouchUpdate = 23, + XI_RawTouchEnd = 24, + XI_BarrierHit = 25, // XI 2.3 + XI_BarrierLeave = 26, + XI_GesturePinchBegin = 27, // XI 2.4 + XI_GesturePinchUpdate = 28, + XI_GesturePinchEnd = 29, + XI_GestureSwipeBegin = 30, + XI_GestureSwipeUpdate = 31, + XI_GestureSwipeEnd = 32, + XI_LASTEVENT = XI_GestureSwipeEnd + } + + // these are normally macros in XI2.h + public static void XISetMask(Span maskBuf, int evt) + => maskBuf[evt >> 3] |= (byte)(1 << (evt & 7)); + + public static bool XIMaskIsSet(Span maskBuf, int evt) + => (maskBuf[evt >> 3] & (byte)(1 << (evt & 7))) != 0; + + public const int XIAllDevices = 0; + public const int XIAllMasterDevices = 1; + + [StructLayout(LayoutKind.Sequential)] + public struct XIEventMask + { + public int deviceid; + public int mask_len; + public IntPtr mask; + } + + [DllImport(XI2)] + public static extern int XISelectEvents(IntPtr display, IntPtr win, ref XIEventMask masks, int num_masks); + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct XIValuatorState + { + public int mask_len; + public byte* mask; + public double* values; + } + + [StructLayout(LayoutKind.Sequential)] + public struct XIRawEvent + { + public int type; + public nuint serial; + public int send_event; + public IntPtr display; + public int extension; + public int evtype; + public nuint time; + public int deviceid; + public int sourceid; + public int detail; + public int flags; + public XIValuatorState valuators; + public unsafe double* raw_values; + } + } +} diff --git a/src/BizHawk.Common/LSB/XlibImports.cs b/src/BizHawk.Common/LSB/XlibImports.cs index 96d504ae55..f760c3196e 100644 --- a/src/BizHawk.Common/LSB/XlibImports.cs +++ b/src/BizHawk.Common/LSB/XlibImports.cs @@ -6,18 +6,18 @@ namespace BizHawk.Common { public static class XlibImports { - private const string LIB = "libX11.so.6"; + private const string XLIB = "libX11.so.6"; - [DllImport(LIB)] + [DllImport(XLIB)] public static extern IntPtr XOpenDisplay(string? display_name); - [DllImport(LIB)] + [DllImport(XLIB)] public static extern int XCloseDisplay(IntPtr display); - [DllImport(LIB)] + [DllImport(XLIB)] public static extern void XLockDisplay(IntPtr display); - [DllImport(LIB)] + [DllImport(XLIB)] public static extern void XUnlockDisplay(IntPtr display); // helper struct for XLockDisplay/XUnlockDisplay @@ -47,7 +47,98 @@ namespace BizHawk.Common } } - [DllImport(LIB)] + [DllImport(XLIB)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool XQueryExtension(IntPtr display, string name, out int major_opcode_return, out int first_event_return, out int first_error_return); + + [DllImport(XLIB)] + public static extern IntPtr XDefaultRootWindow(IntPtr display); + + [DllImport(XLIB)] + public static extern int XPending(IntPtr display); + + public enum XEventTypes : int + { + KeyPress = 2, + KeyRelease = 3, + ButtonPress = 4, + ButtonRelease = 5, + MotionNotify = 6, + EnterNotify = 7, + LeaveNotify = 8, + FocusIn = 9, + FocusOut = 10, + KeymapNotify = 11, + Expose = 12, + GraphicsExpose = 13, + NoExpose = 14, + VisibilityNotify = 15, + CreateNotify = 16, + DestroyNotify = 17, + UnmapNotify = 18, + MapNotify = 19, + MapRequest = 20, + ReparentNotify = 21, + ConfigureNotify = 22, + ConfigureRequest = 23, + GravityNotify = 24, + ResizeRequest = 25, + CirculateNotify = 26, + CirculateRequest = 27, + PropertyNotify = 28, + SelectionClear = 29, + SelectionRequest = 30, + SelectionNotify = 31, + ColormapNotify = 32, + ClientMessage = 33, + MappingNotify = 34, + GenericEvent = 35, + LASTEvent = 36 + } + + [StructLayout(LayoutKind.Sequential)] + public struct XGenericEventCookie + { + public XEventTypes type; + public nuint serial; + public int send_event; + public IntPtr display; + public int extension; + public int evtype; + public uint cookie; + public IntPtr data; + } + + [StructLayout(LayoutKind.Sequential)] + public struct XEventPadding + { + public nint pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; + public nint pad8, pad9, pad10, pad11, pad12, pad13, pad14, pad15; + public nint pad16, pad17, pad18, pad19, pad20, pad21, pad22, pad23; + } + + [StructLayout(LayoutKind.Explicit)] + public struct XEvent + { + [FieldOffset(0)] + public XEventTypes type; + [FieldOffset(0)] + public XGenericEventCookie xcookie; + [FieldOffset(0)] + public XEventPadding pad; + } + + [DllImport(XLIB)] + public static extern int XNextEvent(IntPtr display, out XEvent event_return); + + [DllImport(XLIB)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool XGetEventData(IntPtr display, ref XGenericEventCookie cookie); + + [DllImport(XLIB)] + public static extern int XFreeEventData(IntPtr display, ref XGenericEventCookie cookie); + + [DllImport(XLIB)] public static extern unsafe int XQueryKeymap(IntPtr display, byte* keys_return); // copied from OpenTK @@ -464,15 +555,15 @@ namespace BizHawk.Common public bool same_screen; } - [DllImport(LIB)] + [DllImport(XLIB)] [return: MarshalAs(UnmanagedType.SysUInt)] public static extern Keysym XLookupKeysym(ref XKeyEvent key_event, int index); - [DllImport(LIB)] + [DllImport(XLIB)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool XkbQueryExtension(IntPtr display, out int opcode_rtrn, out int event_rtrn, out int error_rtrn, ref int major_in_out, ref int minor_in_out); - - [DllImport(LIB)] + + [DllImport(XLIB)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool XkbSetDetectableAutoRepeat(IntPtr display, [MarshalAs(UnmanagedType.Bool)] bool detectable, [MarshalAs(UnmanagedType.Bool)] out bool supported_rtrn); @@ -531,16 +622,16 @@ namespace BizHawk.Common public IntPtr geom; } - [DllImport(LIB)] + [DllImport(XLIB)] public static extern unsafe XkbDescRec* XkbAllocKeyboard(IntPtr display); - [DllImport(LIB)] + [DllImport(XLIB)] public static extern unsafe void XkbFreeKeyboard(XkbDescRec* xkb, int which, [MarshalAs(UnmanagedType.Bool)] bool free_all); - [DllImport(LIB)] + [DllImport(XLIB)] public static extern unsafe int XkbGetNames(IntPtr display, uint which, XkbDescRec* xkb); - [DllImport(LIB)] + [DllImport(XLIB)] public static extern Keysym XkbKeycodeToKeysym(IntPtr display, int keycode, int group, int level); } } diff --git a/src/BizHawk.Common/Win32/RawInputImports.cs b/src/BizHawk.Common/Win32/RawInputImports.cs index d060fb2bcf..52175dc7d3 100644 --- a/src/BizHawk.Common/Win32/RawInputImports.cs +++ b/src/BizHawk.Common/Win32/RawInputImports.cs @@ -427,12 +427,22 @@ namespace BizHawk.Common [StructLayout(LayoutKind.Sequential)] public struct RAWMOUSE { - public ushort usFlags; + public MOUSE_FLAGS usFlags; public uint ulButtons; public uint ulRawButtons; public int lLastX; public int lLastY; public uint ulExtraInformation; + + [Flags] + public enum MOUSE_FLAGS : ushort + { + MOVE_RELATIVE = 0, + MOVE_ABSOLUTE = 1, + VIRTUAL_DESKTOP = 2, + ATTRIBUTES_CHANGED = 4, + MOVE_NOCOALESCE = 8, + } } [StructLayout(LayoutKind.Sequential)] @@ -492,7 +502,7 @@ namespace BizHawk.Common [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool RegisterRawInputDevices(ref RAWINPUTDEVICE pRawInputDevice, uint uiNumDevices, int cbSize); + public static extern unsafe bool RegisterRawInputDevices(RAWINPUTDEVICE* pRawInputDevice, uint uiNumDevices, int cbSize); [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] diff --git a/src/BizHawk.Common/macOS/QuartzImports.cs b/src/BizHawk.Common/macOS/QuartzImports.cs index 808a0b5c5b..cd590638ec 100644 --- a/src/BizHawk.Common/macOS/QuartzImports.cs +++ b/src/BizHawk.Common/macOS/QuartzImports.cs @@ -130,5 +130,8 @@ namespace BizHawk.Common [DllImport("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics")] [return: MarshalAs(UnmanagedType.U1)] public static extern bool CGEventSourceKeyState(CGEventSourceStateID stateID, CGKeyCode key); + + [DllImport("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics")] + public static extern void CGGetLastMouseDelta(out int deltaX, out int deltaY); } }