add RAWInput keyboard handler, use it for OSTailoredKeyInputAdapter / SDL2
re-removes hard DirectInput dependency
This commit is contained in:
parent
a320928a6f
commit
5fc37d6aac
|
@ -82,7 +82,8 @@ namespace BizHawk.Bizware.Input
|
|||
return VKeyToDKeyMap.GetValueOrDefault(virtualKey, DInputKey.Unknown);
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyDictionary<DInputKey, DistinctKey> KeyEnumMap = new Dictionary<DInputKey, DistinctKey>
|
||||
// DInputKey is just a scancode so it's used with RAWKeyInput
|
||||
/*private*/ internal static readonly IReadOnlyDictionary<DInputKey, DistinctKey> KeyEnumMap = new Dictionary<DInputKey, DistinctKey>
|
||||
{
|
||||
[DInputKey.D0] = DistinctKey.D0,
|
||||
[DInputKey.D1] = DistinctKey.D1,
|
||||
|
@ -231,7 +232,7 @@ namespace BizHawk.Bizware.Input
|
|||
[DInputKey.Unknown] = DistinctKey.Unknown
|
||||
};
|
||||
|
||||
private static readonly IReadOnlyDictionary<uint, DInputKey> VKeyToDKeyMap = new Dictionary<uint, DInputKey>
|
||||
/*private*/ internal static readonly IReadOnlyDictionary<uint, DInputKey> VKeyToDKeyMap = new Dictionary<uint, DInputKey>
|
||||
{
|
||||
[0x30] = DInputKey.D0,
|
||||
[0x31] = DInputKey.D1,
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace BizHawk.Bizware.Input
|
|||
//break;
|
||||
throw new NotSupportedException("TODO QUARTZ");
|
||||
case OSTailoredCode.DistinctOS.Windows:
|
||||
DKeyInput.Cleanup();
|
||||
RAWKeyInput.Deinitialize();
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
|
@ -54,10 +54,7 @@ namespace BizHawk.Bizware.Input
|
|||
//break;
|
||||
throw new NotSupportedException("TODO QUARTZ");
|
||||
case OSTailoredCode.DistinctOS.Windows:
|
||||
// TODO: Consider if we want to use RAWINPUT API for keyboards instead
|
||||
// Would remove DInput depenency on Windows (DInput gamepads could be considered optional in this sense)
|
||||
// (also, this would be needed for keyboard support with UWP, which doesn't support DInput)
|
||||
DKeyInput.Initialize(mainFormHandle);
|
||||
RAWKeyInput.Initialize();
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
|
@ -80,7 +77,7 @@ namespace BizHawk.Bizware.Input
|
|||
{
|
||||
OSTailoredCode.DistinctOS.Linux => X11KeyInput.Update(),
|
||||
OSTailoredCode.DistinctOS.macOS => throw new NotSupportedException("TODO QUARTZ"),
|
||||
OSTailoredCode.DistinctOS.Windows => DKeyInput.Update(_config ?? throw new(nameof(ProcessHostKeyboards) + " called before the global config was passed")),
|
||||
OSTailoredCode.DistinctOS.Windows => RAWKeyInput.Update(_config ?? throw new(nameof(ProcessHostKeyboards) + " called before the global config was passed")),
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
#nullable enable
|
||||
|
||||
using System;
|
||||
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.Win32Imports;
|
||||
|
||||
using RAWKey = Vortice.DirectInput.Key;
|
||||
|
||||
namespace BizHawk.Bizware.Input
|
||||
{
|
||||
internal static class RAWKeyInput
|
||||
{
|
||||
private static volatile bool _isInit;
|
||||
private static IntPtr _rawInputWindowAtom;
|
||||
private static IntPtr _rawInputWindow;
|
||||
private static bool _handleAlternativeKeyboardLayouts;
|
||||
private static List<KeyEvent> _keyEvents = new();
|
||||
|
||||
private static readonly WNDPROC _wndProc = WndProc;
|
||||
private static readonly object _lockObj = new();
|
||||
|
||||
private static unsafe IntPtr WndProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
const uint WM_INPUT = 0x00FF;
|
||||
|
||||
if (uMsg != WM_INPUT)
|
||||
{
|
||||
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
if (GetRawInputData(lParam, RID.INPUT, IntPtr.Zero,
|
||||
out var size, Marshal.SizeOf<RAWINPUTHEADER>()) == -1)
|
||||
{
|
||||
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
// don't think size should ever be this big, but just in case
|
||||
var buffer = size > 1024
|
||||
? new byte[size]
|
||||
: stackalloc byte[size];
|
||||
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
var input = (RAWINPUT*)p;
|
||||
|
||||
if (GetRawInputData(lParam, RID.INPUT, input,
|
||||
ref size, Marshal.SizeOf<RAWINPUTHEADER>()) == -1)
|
||||
{
|
||||
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
if (input->header.dwType == RAWINPUTHEADER.RIM_TYPE.KEYBOARD && input->data.keyboard.Flags <= RAWKEYBOARD.RIM_KEY.E1)
|
||||
{
|
||||
var rawKey = _handleAlternativeKeyboardLayouts
|
||||
? DKeyInput.VKeyToDKeyMap.GetValueOrDefault(input->data.keyboard.VKey, RAWKey.Unknown)
|
||||
: (RAWKey)(input->data.keyboard.MakeCode |
|
||||
(input->data.keyboard.Flags >= RAWKEYBOARD.RIM_KEY.E0 ? 0x80 : 0));
|
||||
|
||||
if (DKeyInput.KeyEnumMap.TryGetValue(rawKey, out var key) && key != DistinctKey.Unknown)
|
||||
{
|
||||
_keyEvents.Add(new(key, input->data.keyboard.Flags is RAWKEYBOARD.RIM_KEY.MAKE or RAWKEYBOARD.RIM_KEY.E0));
|
||||
}
|
||||
}
|
||||
|
||||
return DefRawInputProc(input, 0, Marshal.SizeOf<RAWINPUTHEADER>());
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateRawInputWindow()
|
||||
{
|
||||
const int WS_CHILD = 0x40000000;
|
||||
var window = CreateWindowEx(
|
||||
dwExStyle: 0,
|
||||
lpClassName: _rawInputWindowAtom,
|
||||
lpWindowName: "RAWKeyInput",
|
||||
dwStyle: WS_CHILD,
|
||||
X: 0,
|
||||
Y: 0,
|
||||
nWidth: 1,
|
||||
nHeight: 1,
|
||||
hWndParent: HWND_MESSAGE,
|
||||
hMenu: IntPtr.Zero,
|
||||
hInstance: GetModuleHandle(null),
|
||||
lpParam: IntPtr.Zero);
|
||||
|
||||
if (window == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create RAWINPUT window");
|
||||
}
|
||||
|
||||
var ri = new RAWINPUTDEVICE[1];
|
||||
ri[0].usUsagePage = RAWINPUTDEVICE.HidUsagePage.GENERIC;
|
||||
ri[0].usUsage = RAWINPUTDEVICE.HidUsageId.GENERIC_KEYBOARD;
|
||||
ri[0].dwFlags = RAWINPUTDEVICE.RIDEV.INPUTSINK;
|
||||
ri[0].hwndTarget = window;
|
||||
|
||||
if (!RegisterRawInputDevices(ri, 1, Marshal.SizeOf<RAWINPUTDEVICE>()))
|
||||
{
|
||||
DestroyWindow(window);
|
||||
throw new InvalidOperationException("Failed to register RAWINPUTDEVICE");
|
||||
}
|
||||
|
||||
_rawInputWindow = window;
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
if (OSTailoredCode.IsUnixHost)
|
||||
{
|
||||
throw new NotSupportedException("RAWINPUT is Windows only");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
Deinitialize();
|
||||
|
||||
if (_rawInputWindowAtom == IntPtr.Zero)
|
||||
{
|
||||
var wc = default(WNDCLASS);
|
||||
wc.lpfnWndProc = _wndProc;
|
||||
wc.hInstance = GetModuleHandle(null);
|
||||
wc.lpszClassName = "RAWKeyInputClass";
|
||||
|
||||
_rawInputWindowAtom = RegisterClass(ref wc);
|
||||
if (_rawInputWindowAtom == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to register RAWINPUT window class");
|
||||
}
|
||||
|
||||
// we can't use a window created on this thread, as Update is called on a different thread
|
||||
// but we can still test window creation
|
||||
CreateRawInputWindow(); // this will throw if window creation or rawinput registering fails
|
||||
DestroyWindow(_rawInputWindow);
|
||||
_rawInputWindow = IntPtr.Zero;
|
||||
|
||||
_isInit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Deinitialize()
|
||||
{
|
||||
lock (_lockObj)
|
||||
{
|
||||
if (_rawInputWindow != IntPtr.Zero)
|
||||
{
|
||||
// Can't use DestroyWindow, that's only allowed in the thread that created the window!
|
||||
const int WM_CLOSE = 0x0010;
|
||||
PostMessage(_rawInputWindow, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
|
||||
_rawInputWindow = IntPtr.Zero;
|
||||
}
|
||||
|
||||
_keyEvents.Clear();
|
||||
_isInit = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<KeyEvent> Update(Config config)
|
||||
{
|
||||
lock (_lockObj)
|
||||
{
|
||||
if (!_isInit)
|
||||
{
|
||||
return Enumerable.Empty<KeyEvent>();
|
||||
}
|
||||
|
||||
if (_rawInputWindow == IntPtr.Zero)
|
||||
{
|
||||
CreateRawInputWindow();
|
||||
}
|
||||
|
||||
_handleAlternativeKeyboardLayouts = config.HandleAlternateKeyboardLayouts;
|
||||
|
||||
while (PeekMessage(out var msg, _rawInputWindow, 0, 0, PM_REMOVE))
|
||||
{
|
||||
TranslateMessage(ref msg);
|
||||
DispatchMessage(ref msg);
|
||||
}
|
||||
|
||||
var ret = _keyEvents;
|
||||
_keyEvents = new();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ namespace BizHawk.Common
|
|||
{
|
||||
public const int MAX_PATH = 260;
|
||||
public const uint PM_REMOVE = 0x0001U;
|
||||
public static readonly IntPtr HWND_MESSAGE = new(-3);
|
||||
|
||||
public delegate int BFFCALLBACK(IntPtr hwnd, uint uMsg, IntPtr lParam, IntPtr lpData);
|
||||
|
||||
|
@ -98,6 +99,144 @@ namespace BizHawk.Common
|
|||
public int y;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RAWINPUTDEVICE
|
||||
{
|
||||
public HidUsagePage usUsagePage;
|
||||
public HidUsageId usUsage;
|
||||
public RIDEV dwFlags;
|
||||
public IntPtr hwndTarget;
|
||||
|
||||
public enum HidUsagePage : ushort
|
||||
{
|
||||
GENERIC = 1,
|
||||
GAME = 5,
|
||||
LED = 8,
|
||||
BUTTON = 9,
|
||||
}
|
||||
|
||||
public enum HidUsageId : ushort
|
||||
{
|
||||
GENERIC_POINTER = 1,
|
||||
GENERIC_MOUSE = 2,
|
||||
GENERIC_JOYSTICK = 4,
|
||||
GENERIC_GAMEPAD = 5,
|
||||
GENERIC_KEYBOARD = 6,
|
||||
GENERIC_KEYPAD = 7,
|
||||
GENERIC_MULTI_AXIS_CONTROLLER = 8,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum RIDEV : int
|
||||
{
|
||||
REMOVE = 0x00000001,
|
||||
EXCLUDE = 0x00000010,
|
||||
PAGEONLY = 0x00000020,
|
||||
NOLEGACY = PAGEONLY | EXCLUDE,
|
||||
INPUTSINK = 0x00000100,
|
||||
CAPTUREMOUSE = 0x00000200,
|
||||
NOHOTKEYS = CAPTUREMOUSE,
|
||||
APPKEYS = 0x00000400,
|
||||
EXINPUTSINK = 0x00001000,
|
||||
DEVNOTIFY = 0x00002000,
|
||||
}
|
||||
}
|
||||
|
||||
public enum RID : uint
|
||||
{
|
||||
HEADER = 0x10000005,
|
||||
INPUT = 0x10000003,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RAWINPUTHEADER
|
||||
{
|
||||
public RIM_TYPE dwType;
|
||||
public uint dwSize;
|
||||
public IntPtr hDevice;
|
||||
public IntPtr wParam;
|
||||
|
||||
public enum RIM_TYPE : uint
|
||||
{
|
||||
MOUSE = 0,
|
||||
KEYBOARD = 1,
|
||||
HID = 2,
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RAWMOUSE
|
||||
{
|
||||
public ushort usFlags;
|
||||
public uint ulButtons;
|
||||
public uint ulRawButtons;
|
||||
public int lLastX;
|
||||
public int lLastY;
|
||||
public uint ulExtraInformation;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RAWKEYBOARD
|
||||
{
|
||||
public ushort MakeCode;
|
||||
public RIM_KEY Flags;
|
||||
public ushort Reserved;
|
||||
public ushort VKey;
|
||||
public uint Message;
|
||||
public uint ExtraInformation;
|
||||
|
||||
public enum RIM_KEY : ushort
|
||||
{
|
||||
MAKE = 0,
|
||||
BREAK = 1,
|
||||
E0 = 2,
|
||||
E1 = 3,
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RAWHID
|
||||
{
|
||||
public uint dwSizeHid;
|
||||
public uint dwCount;
|
||||
public byte bRawData;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct RAWINPUTDATA
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public RAWMOUSE mouse;
|
||||
[FieldOffset(0)]
|
||||
public RAWKEYBOARD keyboard;
|
||||
[FieldOffset(0)]
|
||||
public RAWHID hid;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RAWINPUT
|
||||
{
|
||||
public RAWINPUTHEADER header;
|
||||
public RAWINPUTDATA data;
|
||||
}
|
||||
|
||||
public delegate IntPtr WNDPROC(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct WNDCLASS
|
||||
{
|
||||
public uint style;
|
||||
public WNDPROC lpfnWndProc;
|
||||
public int cbClsExtra;
|
||||
public int cbWndExtra;
|
||||
public IntPtr hInstance;
|
||||
public IntPtr hIcon;
|
||||
public IntPtr hCursor;
|
||||
public IntPtr hbrBackground;
|
||||
public string lpszMenuName;
|
||||
public string lpszClassName;
|
||||
}
|
||||
|
||||
[Guid("00000002-0000-0000-C000-000000000046")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IMalloc
|
||||
|
@ -113,9 +252,23 @@ namespace BizHawk.Common
|
|||
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern uint _control87(uint @new, uint mask);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern IntPtr CreateWindowEx(int dwExStyle, IntPtr lpClassName, string lpWindowName,
|
||||
int dwStyle, int X, int Y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "DeleteFileW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern bool DeleteFileW([MarshalAs(UnmanagedType.LPWStr)] string lpFileName);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern unsafe IntPtr DefRawInputProc(RAWINPUT* paRawInput, int nInput, int cbSizeHeader);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool DestroyWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern IntPtr DispatchMessage([In] ref MSG lpMsg);
|
||||
|
||||
|
@ -125,12 +278,21 @@ namespace BizHawk.Common
|
|||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr GetActiveWindow();
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern IntPtr GetModuleHandle(string lpModuleName);
|
||||
|
||||
[DllImport("kernel32", SetLastError = true, EntryPoint = "GetProcAddress")]
|
||||
public static extern IntPtr GetProcAddressOrdinal(IntPtr hModule, IntPtr procName);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr GetProcessHeap();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern int GetRawInputData(IntPtr hRawInput, RID uiCommand, IntPtr pData, out int bSize, int cbSizeHeader);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern unsafe int GetRawInputData(IntPtr hRawInput, RID uiCommand, RAWINPUT* pData, ref int bSize, int cbSizeHeader);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = false)]
|
||||
public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, int dwBytes);
|
||||
|
||||
|
@ -157,6 +319,16 @@ namespace BizHawk.Common
|
|||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern IntPtr PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern IntPtr RegisterClass([In] ref WNDCLASS lpWndClass);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool RegisterRawInputDevices(RAWINPUTDEVICE[] pRawInputDevices, uint uiNumDevices, int cbSize);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, ref HDITEM lParam);
|
||||
|
||||
|
|
Loading…
Reference in New Issue