153 lines
5.8 KiB
C
153 lines
5.8 KiB
C
|
|
//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 <windows.h>
|
||
|
|
#include <roapi.h>
|
||
|
|
#include <winstring.h>
|
||
|
|
#include <wchar.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
// Defines the interface IDs the headers below declare, in this file.
|
||
|
|
#include <initguid.h>
|
||
|
|
#include <windows.gaming.input.h>
|
||
|
|
#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);
|
||
|
|
}
|