Begin to add raw mouse input to input system
native api side done. trying to map it probably interferes with absolute inputs, needs some global relative mouse sensitivity setting for input translation purposes. hopefully everything else doesnt break
This commit is contained in:
parent
efcbe54f36
commit
2f39991b44
|
@ -9,6 +9,6 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.SDL2-CS" ExcludeAssets="native;contentFiles" />
|
||||
<ProjectReference Include="$(ProjectDir)../BizHawk.Client.Common/BizHawk.Client.Common.csproj" />
|
||||
<ProjectReference Include="$(ProjectDir)../BizHawk.Common/BizHawk.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#nullable enable
|
||||
|
||||
namespace BizHawk.Bizware.Input
|
||||
{
|
||||
[Flags]
|
||||
public enum HostInputFocus
|
||||
{
|
||||
None = 0,
|
||||
Mouse = 1,
|
||||
Keyboard = 2,
|
||||
Pad = 4
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
namespace BizHawk.Bizware.Input
|
||||
{
|
||||
/// <remarks>this was easier than trying to make static classes instantiable...</remarks>
|
||||
/// TODO: Reconsider if we want to hand over the main form handle
|
||||
|
@ -18,17 +18,17 @@ namespace BizHawk.Client.Common
|
|||
/// <remarks>keys are pad prefixes "X# "/"J# " (with the trailing space)</remarks>
|
||||
IReadOnlyDictionary<string, IReadOnlyCollection<string>> GetHapticsChannels();
|
||||
|
||||
void ReInitGamepads(IntPtr mainFormHandle);
|
||||
|
||||
void PreprocessHostGamepads();
|
||||
|
||||
void ProcessHostGamepads(Action<string?, bool, ClientInputFocus> handleButton, Action<string?, int> handleAxis);
|
||||
void ProcessHostGamepads(Action<string?, bool, HostInputFocus> handleButton, Action<string?, int> handleAxis);
|
||||
|
||||
IEnumerable<KeyEvent> ProcessHostKeyboards();
|
||||
|
||||
(int DeltaX, int DeltaY) ProcessHostMice();
|
||||
|
||||
/// <remarks>implementors may store this for use during the next <see cref="ProcessHostGamepads"/> call</remarks>
|
||||
void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot);
|
||||
|
||||
void UpdateConfig(Config config);
|
||||
void SetAlternateKeyboardLayoutEnableCallback(Func<bool> getHandleAlternateKeyboardLayouts);
|
||||
}
|
||||
}
|
|
@ -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<KeyEvent> PendingKeyEvents = new();
|
||||
private static readonly List<KeyEvent> PendingKeyEvents = [ ];
|
||||
private static bool IPCActive;
|
||||
|
||||
private static void IPCThread()
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
#nullable enable
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Client.Common;
|
||||
|
||||
namespace BizHawk.Bizware.Input
|
||||
{
|
||||
internal interface IKeyInput : IDisposable
|
||||
{
|
||||
IEnumerable<KeyEvent> Update(bool handleAltKbLayouts);
|
||||
}
|
||||
}
|
|
@ -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"),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
// ReSharper disable CommentTypo
|
||||
// ReSharper disable IdentifierTypo
|
||||
// ReSharper disable UnusedMember.Global
|
||||
namespace BizHawk.Client.Common
|
||||
namespace BizHawk.Bizware.Input
|
||||
{
|
||||
/// <summary>values are one-to-one with <c>System.Windows.Input.Key</c> except <see cref="NumPadEnter"/> and <see cref="Unknown"/> which were added for this project</summary>
|
||||
/// <remarks>copied from MIT-licensed WPF source: https://github.com/dotnet/wpf/blob/49bb41ad83abeb5ae22e4c59d0f43c1287acac00/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Input/Key.cs</remarks>
|
|
@ -0,0 +1,13 @@
|
|||
#nullable enable
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Bizware.Input
|
||||
{
|
||||
internal interface IKeyMouseInput : IDisposable
|
||||
{
|
||||
IEnumerable<KeyEvent> UpdateKeyInputs(bool handleAltKbLayouts);
|
||||
|
||||
(int DeltaX, int DeltaY) UpdateMouseInput();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#nullable enable
|
||||
|
||||
namespace BizHawk.Bizware.Input
|
||||
{
|
||||
public readonly record struct KeyEvent(DistinctKey Key, bool Pressed);
|
||||
}
|
|
@ -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"),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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<KeyEvent> Update(bool handleAltKbLayouts)
|
||||
public IEnumerable<KeyEvent> UpdateKeyInputs(bool handleAltKbLayouts)
|
||||
{
|
||||
var keyEvents = new List<KeyEvent>();
|
||||
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<CGKeyCode, DistinctKey> KeyEnumMap = new Dictionary<CGKeyCode, DistinctKey>
|
||||
{
|
||||
[CGKeyCode.kVK_ANSI_A] = DistinctKey.A,
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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<KeyEvent> _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<RAWINPUTDEVICE>()))
|
||||
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<RAWINPUT>() + RawInputBufferDataOffset) * 16;
|
||||
RawInputBufferSize = (Unsafe.SizeOf<RAWINPUT>() + RawInputBufferDataOffset) * 16;
|
||||
RawInputBuffer = Marshal.AllocCoTaskMem(RawInputBufferSize);
|
||||
}
|
||||
|
||||
|
@ -261,7 +297,7 @@ namespace BizHawk.Bizware.Input
|
|||
}
|
||||
}
|
||||
|
||||
public IEnumerable<KeyEvent> Update(bool handleAltKbLayouts)
|
||||
public IEnumerable<KeyEvent> 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,
|
|
@ -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<X11.Display>()!; // hmm
|
||||
// _ = Unsafe.AsRef<X11.Display>()!; // 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<byte> 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<KeyEvent> Update(bool handleAltKbLayouts)
|
||||
public unsafe IEnumerable<KeyEvent> UpdateKeyInputs(bool handleAltKbLayouts)
|
||||
{
|
||||
lock (LockObj)
|
||||
{
|
||||
// Can't update without a display connection
|
||||
if (Display == IntPtr.Zero)
|
||||
{
|
||||
return Enumerable.Empty<KeyEvent>();
|
||||
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<KeyEvent>();
|
||||
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<byte>(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++)
|
|
@ -3,54 +3,53 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using BizHawk.Client.Common;
|
||||
|
||||
namespace BizHawk.Bizware.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
public abstract class OSTailoredKeyInputAdapter : IHostInputAdapter
|
||||
public abstract class OSTailoredKeyMouseInputAdapter : IHostInputAdapter
|
||||
{
|
||||
private IKeyInput? _keyInput;
|
||||
protected Config? _config;
|
||||
private IKeyMouseInput? _keyMouseInput;
|
||||
protected Func<bool>? _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<string, IReadOnlyCollection<string>> GetHapticsChannels();
|
||||
|
||||
public abstract void ReInitGamepads(IntPtr mainFormHandle);
|
||||
|
||||
public abstract void PreprocessHostGamepads();
|
||||
|
||||
public abstract void ProcessHostGamepads(Action<string?, bool, ClientInputFocus> handleButton, Action<string?, int> handleAxis);
|
||||
public abstract void ProcessHostGamepads(Action<string?, bool, HostInputFocus> handleButton, Action<string?, int> handleAxis);
|
||||
|
||||
public virtual IEnumerable<KeyEvent> 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<bool> getHandleAlternateKeyboardLayouts)
|
||||
=> _getHandleAlternateKeyboardLayouts = getHandleAlternateKeyboardLayouts;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<string> SDL2_HAPTIC_CHANNEL_NAMES = new[] { "Left", "Right" };
|
||||
private static readonly IReadOnlyCollection<string> SDL2_HAPTIC_CHANNEL_NAMES = [ "Left", "Right" ];
|
||||
|
||||
private IReadOnlyDictionary<string, int> _lastHapticsSnapshot = new Dictionary<string, int>();
|
||||
|
||||
|
@ -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<string?, bool, ClientInputFocus> handleButton, Action<string?, int> handleAxis)
|
||||
public override void ProcessHostGamepads(Action<string?, bool, HostInputFocus> handleButton, Action<string?, int> 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<KeyEvent>();
|
||||
: [ ];
|
||||
}
|
||||
|
||||
public override void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<PackageReference Include="System.CommandLine" />
|
||||
<ProjectReference Include="$(ProjectDir)../BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj" />
|
||||
<ProjectReference Include="$(ProjectDir)../BizHawk.Bizware.Graphics/BizHawk.Bizware.Graphics.csproj" />
|
||||
<ProjectReference Include="$(ProjectDir)../BizHawk.Bizware.Input/BizHawk.Bizware.Input.csproj" />
|
||||
<EmbeddedResource Include="Resources/**/*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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}";
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
/// </summary>
|
||||
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<Control> _wantingMouseFocus = new HashSet<Control>();
|
||||
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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<byte> maskBuf, int evt)
|
||||
=> maskBuf[evt >> 3] |= (byte)(1 << (evt & 7));
|
||||
|
||||
public static bool XIMaskIsSet(Span<byte> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue