#include "SDL_internal.h"
#if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT)
#include "../SDL_sysjoystick.h"
#include "../../thread/SDL_systhread.h"
#include "../../core/windows/SDL_windows.h"
#include "../../core/windows/SDL_hid.h"
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
#include <dbt.h>
#endif
#define INITGUID
#include "SDL_windowsjoystick_c.h"
#include "SDL_dinputjoystick_c.h"
#include "SDL_xinputjoystick_c.h"
#include "SDL_rawinputjoystick_c.h"
#include "../../haptic/windows/SDL_dinputhaptic_c.h"
#ifndef DEVICE_NOTIFY_WINDOW_HANDLE
#define DEVICE_NOTIFY_WINDOW_HANDLE 0x00000000
#endif
static bool s_bJoystickThread = false;
static SDL_Condition *s_condJoystickThread = NULL;
static SDL_Mutex *s_mutexJoyStickEnum = NULL;
static SDL_Thread *s_joystickThread = NULL;
static bool s_bJoystickThreadQuit = false;
static Uint64 s_lastDeviceChange = 0;
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2L, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
JoyStick_DeviceData *SYS_Joystick;
static bool WindowsDeviceChanged(void)
{
return (s_lastDeviceChange != WIN_GetLastDeviceNotification());
}
static void SetWindowsDeviceChanged(void)
{
s_lastDeviceChange = 0;
}
void WINDOWS_RAWINPUTEnabledChanged(void)
{
SetWindowsDeviceChanged();
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
typedef struct
{
HRESULT coinitialized;
WNDCLASSEX wincl;
HWND messageWindow;
HDEVNOTIFY hNotify;
} SDL_DeviceNotificationData;
#define IDT_SDL_DEVICE_CHANGE_TIMER_1 1200
#define IDT_SDL_DEVICE_CHANGE_TIMER_2 1201
static LRESULT CALLBACK SDL_PrivateJoystickDetectProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_DEVICECHANGE:
switch (wParam) {
case DBT_DEVICEARRIVAL:
case DBT_DEVICEREMOVECOMPLETE:
if (((DEV_BROADCAST_HDR *)lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
SetTimer(hwnd, IDT_SDL_DEVICE_CHANGE_TIMER_1, 300, NULL);
SetTimer(hwnd, IDT_SDL_DEVICE_CHANGE_TIMER_2, 2000, NULL);
}
break;
}
return true;
case WM_TIMER:
if (wParam == IDT_SDL_DEVICE_CHANGE_TIMER_1 ||
wParam == IDT_SDL_DEVICE_CHANGE_TIMER_2) {
KillTimer(hwnd, wParam);
SetWindowsDeviceChanged();
return true;
}
break;
}
#ifdef SDL_JOYSTICK_RAWINPUT
return CallWindowProc(RAWINPUT_WindowProc, hwnd, msg, wParam, lParam);
#else
return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam);
#endif
}
static void SDL_CleanupDeviceNotification(SDL_DeviceNotificationData *data)
{
#ifdef SDL_JOYSTICK_RAWINPUT
RAWINPUT_UnregisterNotifications();
#endif
if (data->hNotify) {
UnregisterDeviceNotification(data->hNotify);
}
if (data->messageWindow) {
DestroyWindow(data->messageWindow);
}
UnregisterClass(data->wincl.lpszClassName, data->wincl.hInstance);
if (data->coinitialized == S_OK) {
WIN_CoUninitialize();
}
}
static bool SDL_CreateDeviceNotification(SDL_DeviceNotificationData *data)
{
DEV_BROADCAST_DEVICEINTERFACE dbh;
SDL_zerop(data);
data->coinitialized = WIN_CoInitialize();
data->wincl.hInstance = GetModuleHandle(NULL);
data->wincl.lpszClassName = TEXT("Message");
data->wincl.lpfnWndProc = SDL_PrivateJoystickDetectProc; data->wincl.cbSize = sizeof(WNDCLASSEX);
if (!RegisterClassEx(&data->wincl)) {
WIN_SetError("Failed to create register class for joystick autodetect");
SDL_CleanupDeviceNotification(data);
return false;
}
data->messageWindow = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
if (!data->messageWindow) {
WIN_SetError("Failed to create message window for joystick autodetect");
SDL_CleanupDeviceNotification(data);
return false;
}
SDL_zero(dbh);
dbh.dbcc_size = sizeof(dbh);
dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
dbh.dbcc_classguid = GUID_DEVINTERFACE_HID;
data->hNotify = RegisterDeviceNotification(data->messageWindow, &dbh, DEVICE_NOTIFY_WINDOW_HANDLE);
if (!data->hNotify) {
WIN_SetError("Failed to create notify device for joystick autodetect");
SDL_CleanupDeviceNotification(data);
return false;
}
#ifdef SDL_JOYSTICK_RAWINPUT
RAWINPUT_RegisterNotifications(data->messageWindow);
#endif
return true;
}
static bool SDL_WaitForDeviceNotification(SDL_DeviceNotificationData *data, SDL_Mutex *mutex)
{
MSG msg;
int lastret = 1;
if (!data->messageWindow) {
return false; }
SDL_UnlockMutex(mutex);
while (lastret > 0 && !WindowsDeviceChanged()) {
lastret = GetMessage(&msg, NULL, 0, 0); if (lastret > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
SDL_LockMutex(mutex);
return (lastret != -1);
}
#endif
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
static SDL_DeviceNotificationData s_notification_data;
#endif
static int SDLCALL SDL_JoystickThread(void *_data)
{
#ifdef SDL_JOYSTICK_XINPUT
bool bOpenedXInputDevices[XUSER_MAX_COUNT];
SDL_zeroa(bOpenedXInputDevices);
#endif
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
if (!SDL_CreateDeviceNotification(&s_notification_data)) {
return 0;
}
#endif
SDL_LockMutex(s_mutexJoyStickEnum);
while (s_bJoystickThreadQuit == false) {
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
if (SDL_WaitForDeviceNotification(&s_notification_data, s_mutexJoyStickEnum) == false) {
#else
{
#endif
#ifdef SDL_JOYSTICK_XINPUT
SDL_WaitConditionTimeout(s_condJoystickThread, s_mutexJoyStickEnum, 1000);
if (SDL_XINPUT_Enabled()) {
Uint8 userId;
for (userId = 0; userId < XUSER_MAX_COUNT; userId++) {
XINPUT_CAPABILITIES capabilities;
const DWORD result = XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities);
const bool available = (result == ERROR_SUCCESS);
if (bOpenedXInputDevices[userId] != available) {
SetWindowsDeviceChanged();
bOpenedXInputDevices[userId] = available;
}
}
}
#else
break;
#endif }
}
SDL_UnlockMutex(s_mutexJoyStickEnum);
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
SDL_CleanupDeviceNotification(&s_notification_data);
#endif
return 1;
}
static bool SDL_StartJoystickThread(void)
{
s_mutexJoyStickEnum = SDL_CreateMutex();
if (!s_mutexJoyStickEnum) {
return false;
}
s_condJoystickThread = SDL_CreateCondition();
if (!s_condJoystickThread) {
return false;
}
s_bJoystickThreadQuit = false;
s_joystickThread = SDL_CreateThread(SDL_JoystickThread, "SDL_joystick", NULL);
if (!s_joystickThread) {
return false;
}
return true;
}
static void SDL_StopJoystickThread(void)
{
if (!s_joystickThread) {
return;
}
SDL_LockMutex(s_mutexJoyStickEnum);
s_bJoystickThreadQuit = true;
SDL_BroadcastCondition(s_condJoystickThread); SDL_UnlockMutex(s_mutexJoyStickEnum);
PostThreadMessage((DWORD)SDL_GetThreadID(s_joystickThread), WM_QUIT, 0, 0);
SDL_AssertJoysticksLocked();
SDL_UnlockJoysticks();
SDL_WaitThread(s_joystickThread, NULL); SDL_LockJoysticks();
SDL_DestroyCondition(s_condJoystickThread);
s_condJoystickThread = NULL;
SDL_DestroyMutex(s_mutexJoyStickEnum);
s_mutexJoyStickEnum = NULL;
s_joystickThread = NULL;
}
void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device)
{
device->send_add_event = true;
device->nInstanceID = SDL_GetNextObjectID();
device->pNext = SYS_Joystick;
SYS_Joystick = device;
}
void WINDOWS_JoystickDetect(void);
void WINDOWS_JoystickQuit(void);
static bool WINDOWS_JoystickInit(void)
{
if (!SDL_XINPUT_JoystickInit()) {
WINDOWS_JoystickQuit();
return false;
}
if (!SDL_DINPUT_JoystickInit()) {
WINDOWS_JoystickQuit();
return false;
}
WIN_InitDeviceNotification();
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
s_bJoystickThread = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_THREAD, true);
if (s_bJoystickThread) {
if (!SDL_StartJoystickThread()) {
return false;
}
} else {
if (!SDL_CreateDeviceNotification(&s_notification_data)) {
return false;
}
}
#endif
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
s_bJoystickThread = true;
if (!SDL_StartJoystickThread()) {
return false;
}
#endif
SetWindowsDeviceChanged();
WINDOWS_JoystickDetect();
return true;
}
static int WINDOWS_JoystickGetCount(void)
{
int nJoysticks = 0;
JoyStick_DeviceData *device = SYS_Joystick;
while (device) {
nJoysticks++;
device = device->pNext;
}
return nJoysticks;
}
void WINDOWS_JoystickDetect(void)
{
JoyStick_DeviceData *pCurList = NULL;
if (!WindowsDeviceChanged()) {
return; }
if (s_mutexJoyStickEnum) {
SDL_LockMutex(s_mutexJoyStickEnum);
}
s_lastDeviceChange = WIN_GetLastDeviceNotification();
pCurList = SYS_Joystick;
SYS_Joystick = NULL;
SDL_DINPUT_JoystickDetect(&pCurList);
SDL_XINPUT_JoystickDetect(&pCurList);
if (s_mutexJoyStickEnum) {
SDL_UnlockMutex(s_mutexJoyStickEnum);
}
while (pCurList) {
JoyStick_DeviceData *pListNext = NULL;
#ifdef SDL_HAPTIC_DINPUT
#ifdef SDL_JOYSTICK_XINPUT
if (!pCurList->bXInputDevice) {
SDL_DINPUT_HapticMaybeRemoveDevice(&pCurList->dxdevice);
}
#else
SDL_DINPUT_HapticMaybeRemoveDevice(&pCurList->dxdevice);
#endif
#endif
SDL_PrivateJoystickRemoved(pCurList->nInstanceID);
pListNext = pCurList->pNext;
SDL_free(pCurList->joystickname);
SDL_free(pCurList);
pCurList = pListNext;
}
for (pCurList = SYS_Joystick; pCurList; pCurList = pCurList->pNext) {
if (pCurList->send_add_event) {
#ifdef SDL_HAPTIC_DINPUT
#ifdef SDL_JOYSTICK_XINPUT
if (!pCurList->bXInputDevice) {
SDL_DINPUT_HapticMaybeAddDevice(&pCurList->dxdevice);
}
#else
SDL_DINPUT_HapticMaybeAddDevice(&pCurList->dxdevice);
#endif
#endif
SDL_PrivateJoystickAdded(pCurList->nInstanceID);
pCurList->send_add_event = false;
}
}
}
static bool WINDOWS_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
{
if (SDL_DINPUT_JoystickPresent(vendor_id, product_id, version)) {
return true;
}
if (SDL_XINPUT_JoystickPresent(vendor_id, product_id, version)) {
return true;
}
return false;
}
static const char *WINDOWS_JoystickGetDeviceName(int device_index)
{
JoyStick_DeviceData *device = SYS_Joystick;
int index;
for (index = device_index; index > 0; index--) {
device = device->pNext;
}
return device->joystickname;
}
static const char *WINDOWS_JoystickGetDevicePath(int device_index)
{
JoyStick_DeviceData *device = SYS_Joystick;
int index;
for (index = device_index; index > 0; index--) {
device = device->pNext;
}
return device->path;
}
static int WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
{
JoyStick_DeviceData *device = SYS_Joystick;
int index;
for (index = device_index; index > 0; index--) {
device = device->pNext;
}
#ifdef SDL_JOYSTICK_XINPUT
if (device->bXInputDevice) {
return SDL_XINPUT_GetSteamVirtualGamepadSlot(device->XInputUserId);
}
#endif
return device->steam_virtual_gamepad_slot;
}
static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index)
{
#ifdef SDL_JOYSTICK_XINPUT
JoyStick_DeviceData *device = SYS_Joystick;
int index;
for (index = device_index; index > 0; index--) {
device = device->pNext;
}
return device->bXInputDevice ? (int)device->XInputUserId : -1;
#else
return -1;
#endif
}
static void WINDOWS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
{
}
static SDL_GUID WINDOWS_JoystickGetDeviceGUID(int device_index)
{
JoyStick_DeviceData *device = SYS_Joystick;
int index;
for (index = device_index; index > 0; index--) {
device = device->pNext;
}
return device->guid;
}
static SDL_JoystickID WINDOWS_JoystickGetDeviceInstanceID(int device_index)
{
JoyStick_DeviceData *device = SYS_Joystick;
int index;
for (index = device_index; index > 0; index--) {
device = device->pNext;
}
return device->nInstanceID;
}
static bool WINDOWS_JoystickOpen(SDL_Joystick *joystick, int device_index)
{
JoyStick_DeviceData *device = SYS_Joystick;
int index;
for (index = device_index; index > 0; index--) {
device = device->pNext;
}
joystick->hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(struct joystick_hwdata));
if (!joystick->hwdata) {
return false;
}
joystick->hwdata->guid = device->guid;
#ifdef SDL_JOYSTICK_XINPUT
if (device->bXInputDevice) {
return SDL_XINPUT_JoystickOpen(joystick, device);
}
#endif
return SDL_DINPUT_JoystickOpen(joystick, device);
}
static bool WINDOWS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
#ifdef SDL_JOYSTICK_XINPUT
if (joystick->hwdata->bXInputDevice) {
return SDL_XINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
}
#endif
return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
}
static bool WINDOWS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static bool WINDOWS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static bool WINDOWS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static bool WINDOWS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
{
return SDL_Unsupported();
}
static void WINDOWS_JoystickUpdate(SDL_Joystick *joystick)
{
if (!joystick->hwdata) {
return;
}
#ifdef SDL_JOYSTICK_XINPUT
if (joystick->hwdata->bXInputDevice) {
SDL_XINPUT_JoystickUpdate(joystick);
return;
}
#endif
SDL_DINPUT_JoystickUpdate(joystick);
}
static void WINDOWS_JoystickClose(SDL_Joystick *joystick)
{
#ifdef SDL_JOYSTICK_XINPUT
if (joystick->hwdata->bXInputDevice) {
SDL_XINPUT_JoystickClose(joystick);
} else {
SDL_DINPUT_JoystickClose(joystick);
}
#else
SDL_DINPUT_JoystickClose(joystick);
#endif
SDL_free(joystick->hwdata);
}
void WINDOWS_JoystickQuit(void)
{
JoyStick_DeviceData *device = SYS_Joystick;
while (device) {
JoyStick_DeviceData *device_next = device->pNext;
SDL_free(device->joystickname);
SDL_free(device);
device = device_next;
}
SYS_Joystick = NULL;
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
if (s_bJoystickThread) {
SDL_StopJoystickThread();
} else {
SDL_CleanupDeviceNotification(&s_notification_data);
}
#endif
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
if (s_bJoystickThread) {
SDL_StopJoystickThread();
}
#endif
SDL_DINPUT_JoystickQuit();
SDL_XINPUT_JoystickQuit();
WIN_QuitDeviceNotification();
}
static bool WINDOWS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
{
return false;
}
SDL_JoystickDriver SDL_WINDOWS_JoystickDriver = {
WINDOWS_JoystickInit,
WINDOWS_JoystickGetCount,
WINDOWS_JoystickDetect,
WINDOWS_JoystickIsDevicePresent,
WINDOWS_JoystickGetDeviceName,
WINDOWS_JoystickGetDevicePath,
WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot,
WINDOWS_JoystickGetDevicePlayerIndex,
WINDOWS_JoystickSetDevicePlayerIndex,
WINDOWS_JoystickGetDeviceGUID,
WINDOWS_JoystickGetDeviceInstanceID,
WINDOWS_JoystickOpen,
WINDOWS_JoystickRumble,
WINDOWS_JoystickRumbleTriggers,
WINDOWS_JoystickSetLED,
WINDOWS_JoystickSendEffect,
WINDOWS_JoystickSetSensorsEnabled,
WINDOWS_JoystickUpdate,
WINDOWS_JoystickClose,
WINDOWS_JoystickQuit,
WINDOWS_JoystickGetGamepadMapping
};
#else
#ifdef SDL_JOYSTICK_RAWINPUT
#error SDL_JOYSTICK_RAWINPUT requires SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT
#endif
#endif