//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // Windows.Gaming.Input for the Win32 gamepad backend; see // Crafter.Graphics-GamepadWin32.h for why this part is C. #define COBJMACROS #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #include #include #include #include // Defines the interface IDs the headers below declare, in this file. #include #include #include "Crafter.Graphics-GamepadWin32.h" typedef __x_ABI_CWindows_CGaming_CInput_CIGamepad IGamepad; typedef __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics IGamepadStatics; typedef __FIEventHandler_1_Windows__CGaming__CInput__CGamepad GamepadHandler; typedef __FIEventHandler_1_Windows__CGaming__CInput__CGamepadVtbl GamepadHandlerVtbl; _Static_assert(CRAFTER_WGI_BUTTON_MENU == GamepadButtons_Menu && CRAFTER_WGI_BUTTON_RIGHT_THUMBSTICK == GamepadButtons_RightThumbstick && CRAFTER_WGI_BUTTON_DPAD_UP == GamepadButtons_DPadUp && CRAFTER_WGI_BUTTON_A == GamepadButtons_A, "the CRAFTER_WGI_BUTTON_ values are GamepadButtons'"); static IGamepadStatics* statics = NULL; static EventRegistrationToken addedToken; static EventRegistrationToken removedToken; static int started = 0; // The connects and disconnects, which Windows.Gaming.Input reports on its // own threads, until the event loop takes them. static SRWLOCK queueLock = SRWLOCK_INIT; static CrafterWgiEvent* queue = NULL; static size_t queueCount = 0; static size_t queueCapacity = 0; // The GamepadAdded and GamepadRemoved handlers. Static objects, so their // reference counts don't decide their lifetime. typedef struct Handler { const GamepadHandlerVtbl* lpVtbl; int added; } Handler; static HRESULT STDMETHODCALLTYPE HandlerQueryInterface(GamepadHandler* This, REFIID riid, void** out) { if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAgileObject) || IsEqualIID(riid, &IID___FIEventHandler_1_Windows__CGaming__CInput__CGamepad)) { *out = This; return S_OK; } *out = NULL; return E_NOINTERFACE; } static ULONG STDMETHODCALLTYPE HandlerAddRef(GamepadHandler* This) { (void)This; return 2; } static ULONG STDMETHODCALLTYPE HandlerRelease(GamepadHandler* This) { (void)This; return 1; } static HRESULT STDMETHODCALLTYPE HandlerInvoke(GamepadHandler* This, IInspectable* sender, IGamepad* gamepad) { (void)sender; if (gamepad == NULL) return S_OK; const Handler* handler = (const Handler*)This; __x_ABI_CWindows_CGaming_CInput_CIGamepad_AddRef(gamepad); AcquireSRWLockExclusive(&queueLock); if (queueCount == queueCapacity) { size_t capacity = queueCapacity == 0 ? 8 : queueCapacity * 2; CrafterWgiEvent* grown = (CrafterWgiEvent*)realloc(queue, capacity * sizeof(CrafterWgiEvent)); if (grown == NULL) { ReleaseSRWLockExclusive(&queueLock); __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad); return E_OUTOFMEMORY; } queue = grown; queueCapacity = capacity; } queue[queueCount].added = handler->added; queue[queueCount].gamepad = (CrafterWgiGamepad*)gamepad; ++queueCount; ReleaseSRWLockExclusive(&queueLock); return S_OK; } static const GamepadHandlerVtbl handlerVtbl = { HandlerQueryInterface, HandlerAddRef, HandlerRelease, HandlerInvoke, }; static Handler addedHandler = { &handlerVtbl, 1 }; static Handler removedHandler = { &handlerVtbl, 0 }; int CrafterWgiStart(void) { if (started) return statics != NULL; started = 1; // Once per thread. RPC_E_CHANGED_MODE (the host made it a single-threaded // apartment) is fine for Windows.Gaming.Input. RoInitialize(RO_INIT_MULTITHREADED); HSTRING_HEADER header; HSTRING className; const wchar_t* name = RuntimeClass_Windows_Gaming_Input_Gamepad; if (FAILED(WindowsCreateStringReference(name, (UINT32)wcslen(name), &header, &className))) return 0; if (FAILED(RoGetActivationFactory(className, &IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics, (void**)&statics))) { statics = NULL; return 0; } __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_add_GamepadAdded(statics, (GamepadHandler*)&addedHandler, &addedToken); __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_add_GamepadRemoved(statics, (GamepadHandler*)&removedHandler, &removedToken); return 1; } size_t CrafterWgiTakeEvents(CrafterWgiEvent* out, size_t capacity) { AcquireSRWLockExclusive(&queueLock); size_t taken = queueCount < capacity ? queueCount : capacity; for (size_t i = 0; i < taken; ++i) out[i] = queue[i]; for (size_t i = taken; i < queueCount; ++i) queue[i - taken] = queue[i]; queueCount -= taken; ReleaseSRWLockExclusive(&queueLock); return taken; } int CrafterWgiRead(CrafterWgiGamepad* gamepad, CrafterWgiReading* out) { __x_ABI_CWindows_CGaming_CInput_CGamepadReading reading; if (FAILED(__x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading((IGamepad*)gamepad, &reading))) return 0; out->buttons = (uint32_t)reading.Buttons; out->leftTrigger = reading.LeftTrigger; out->rightTrigger = reading.RightTrigger; out->leftStickX = reading.LeftThumbstickX; out->leftStickY = reading.LeftThumbstickY; out->rightStickX = reading.RightThumbstickX; out->rightStickY = reading.RightThumbstickY; return 1; } void CrafterWgiVibrate(CrafterWgiGamepad* gamepad, double low, double high) { __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration = { 0 }; vibration.LeftMotor = low; vibration.RightMotor = high; __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration((IGamepad*)gamepad, vibration); } void CrafterWgiRelease(CrafterWgiGamepad* gamepad) { __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release((IGamepad*)gamepad); }