using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using RioJoy.Core.Mapping;
using MouseButtonId = RioJoy.Core.Mapping.MouseButton;
namespace RioJoy.Core.Output;
///
/// backed by the Win32 SendInput API. Keys are
/// sent by scancode (VK → scancode via MapVirtualKey), matching the
/// legacy Key function (riovjoy2.cpp#L2055), so games that read raw
/// scancodes (most do) see the keypress. Targets the interactive session, which
/// is why RIOJoy is a tray app rather than a service (see docs/PLAN.md risks).
///
[SupportedOSPlatform("windows")]
public sealed class SendInputSink : IInputSink
{
public void KeyDown(byte virtualKey, bool extended) => SendKey(virtualKey, extended, keyUp: false);
public void KeyUp(byte virtualKey, bool extended) => SendKey(virtualKey, extended, keyUp: true);
public void MouseMove(int dx, int dy)
{
var input = new INPUT
{
type = INPUT_MOUSE,
U = { mi = { dx = dx, dy = dy, dwFlags = MOUSEEVENTF_MOVE } },
};
Send(input);
}
public void MouseButton(MouseButton button, bool down)
{
uint flags = (button, down) switch
{
(MouseButtonId.Left, true) => MOUSEEVENTF_LEFTDOWN,
(MouseButtonId.Left, false) => MOUSEEVENTF_LEFTUP,
(MouseButtonId.Right, true) => MOUSEEVENTF_RIGHTDOWN,
(MouseButtonId.Right, false) => MOUSEEVENTF_RIGHTUP,
_ => 0,
};
var input = new INPUT
{
type = INPUT_MOUSE,
U = { mi = { dwFlags = flags } },
};
Send(input);
}
private static void SendKey(byte virtualKey, bool extended, bool keyUp)
{
uint flags = KEYEVENTF_SCANCODE;
if (keyUp) flags |= KEYEVENTF_KEYUP;
if (extended) flags |= KEYEVENTF_EXTENDEDKEY;
var input = new INPUT
{
type = INPUT_KEYBOARD,
U =
{
ki = new KEYBDINPUT
{
wVk = 0,
wScan = (ushort)MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC),
dwFlags = flags,
},
},
};
Send(input);
}
private static void Send(INPUT input)
{
Span inputs = stackalloc INPUT[1];
inputs[0] = input;
SendInput(1, inputs, Marshal.SizeOf());
}
// --- Win32 interop --------------------------------------------------------
private const uint INPUT_MOUSE = 0;
private const uint INPUT_KEYBOARD = 1;
private const uint KEYEVENTF_EXTENDEDKEY = 0x0001;
private const uint KEYEVENTF_KEYUP = 0x0002;
private const uint KEYEVENTF_SCANCODE = 0x0008;
private const uint MOUSEEVENTF_MOVE = 0x0001;
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
private const uint MOUSEEVENTF_LEFTUP = 0x0004;
private const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
private const uint MOUSEEVENTF_RIGHTUP = 0x0010;
private const uint MAPVK_VK_TO_VSC = 0;
[StructLayout(LayoutKind.Sequential)]
private struct INPUT
{
public uint type;
public InputUnion U;
}
[StructLayout(LayoutKind.Explicit)]
private struct InputUnion
{
[FieldOffset(0)] public MOUSEINPUT mi;
[FieldOffset(0)] public KEYBDINPUT ki;
}
[StructLayout(LayoutKind.Sequential)]
private struct MOUSEINPUT
{
public int dx;
public int dy;
public uint mouseData;
public uint dwFlags;
public uint time;
public nuint dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
private struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public nuint dwExtraInfo;
}
[DllImport("user32.dll", SetLastError = true)]
private static extern uint SendInput(uint nInputs, Span pInputs, int cbSize);
[DllImport("user32.dll")]
private static extern uint MapVirtualKey(uint uCode, uint uMapType);
}