diff --git a/.github/workflows/create-test-plan.py b/.github/workflows/create-test-plan.py index 26c6949df1..06bd736d42 100755 --- a/.github/workflows/create-test-plan.py +++ b/.github/workflows/create-test-plan.py @@ -631,7 +631,7 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool, ctest_args "-ffile-prefix-map=${PWD}=/SDL", )) job.ldflags.extend(( - "--source-map-base", "/", + "--source-map-base", "/", "-s", "ASYNCIFY", )) pretest_cmd.extend(( "# Start local HTTP server", diff --git a/CMakeLists.txt b/CMakeLists.txt index 51542b8ba6..b84ca93ec1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1760,6 +1760,10 @@ elseif(EMSCRIPTEN) ) endif() + if(SDL_HIDAPI) + CheckHIDAPI() + endif() + if(SDL_JOYSTICK) set(SDL_JOYSTICK_EMSCRIPTEN 1) sdl_glob_sources( diff --git a/src/hidapi/SDL_hidapi.c b/src/hidapi/SDL_hidapi.c index 0dda4d4ba1..7e1ed944d1 100644 --- a/src/hidapi/SDL_hidapi.c +++ b/src/hidapi/SDL_hidapi.c @@ -595,6 +595,8 @@ typedef struct PLATFORM_hid_device_ PLATFORM_hid_device; #include "SDL_hidapi_android.h" #elif defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) #include "SDL_hidapi_ios.h" +#elif defined(SDL_PLATFORM_EMSCRIPTEN) +#include "SDL_hidapi_emscripten.h" #endif #undef api_version diff --git a/src/hidapi/SDL_hidapi_emscripten.h b/src/hidapi/SDL_hidapi_emscripten.h new file mode 100644 index 0000000000..dbf6f1e1fa --- /dev/null +++ b/src/hidapi/SDL_hidapi_emscripten.h @@ -0,0 +1,25 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#undef HIDAPI_H__ +#include "emscripten/hid.c" +#define HAVE_PLATFORM_BACKEND 1 +#define udev_ctx 1 diff --git a/src/hidapi/emscripten/hid.c b/src/hidapi/emscripten/hid.c new file mode 100644 index 0000000000..99466b2391 --- /dev/null +++ b/src/hidapi/emscripten/hid.c @@ -0,0 +1,479 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +#include "../hidapi/hidapi.h" +#include +#include +#include + +EM_JS_DEPS(hidapi, "$stringToUTF32"); + +#if 0 +#define HIDAPI_WEBHID_DEBUG +#endif + +struct hid_device_ { + int device_id; + unsigned char *last_report; + size_t last_report_length; + struct hid_device_info* device_info; +}; + +static struct hid_api_version api_version = { + .major = HID_API_VERSION_MAJOR, + .minor = HID_API_VERSION_MINOR, + .patch = HID_API_VERSION_PATCH +}; + +static int initialized = 0; +static int webhid_supported = 0; + +static hid_device *new_hid_device(void) +{ + hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + if (dev == NULL) { + return NULL; + } + + dev->device_id = -1; + dev->last_report = NULL; + dev->last_report_length = 0; + + return dev; +} + +HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void) +{ + return &api_version; +} + +HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) +{ + return HID_API_VERSION_STR; +} + +int HID_API_EXPORT hid_init(void) +{ + if (!initialized) { + webhid_supported = MAIN_THREAD_EM_ASM_INT({ + return "hid" in navigator; + }); + } + return webhid_supported ? 0 : -1; +} + +int HID_API_EXPORT hid_exit(void) +{ + return 0; +} + +static struct hid_device_info * create_device_info_for_device(int device_id) +{ + struct hid_device_info *root = NULL; + struct hid_device_info *cur_dev = NULL; + char path[16]; + wchar_t product_string[128]; + int ignore = 0; + int input_report_count; + + ignore = MAIN_THREAD_EM_ASM_INT({ + let device = window._hidDeviceList[$0]; + if (!device) { + return true; + } + return false; + }, device_id); + + if (ignore) + goto end; + + /* Create the record. */ + root = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + if (!root) + goto end; + + cur_dev = root; + + snprintf(path, sizeof(path), "hid%d", device_id); + cur_dev->path = strdup(path); + + cur_dev->vendor_id = MAIN_THREAD_EM_ASM_INT({ + return window._hidDeviceList[$0].vendorId; + }, device_id); + cur_dev->product_id = MAIN_THREAD_EM_ASM_INT({ + return window._hidDeviceList[$0].productId; + }, device_id); + + cur_dev->serial_number = NULL; + cur_dev->release_number = 0; + cur_dev->manufacturer_string = NULL; + + MAIN_THREAD_EM_ASM({ + stringToUTF32(window._hidDeviceList[$0].productName, $1, Number($2)); + }, device_id, product_string, sizeof(product_string)); + + cur_dev->product_string = wcsdup(product_string); + + cur_dev->usage_page = MAIN_THREAD_EM_ASM_INT({ + return window._hidDeviceList[$0].collections[0].usagePage; + }, device_id); + cur_dev->usage = MAIN_THREAD_EM_ASM_INT({ + return window._hidDeviceList[$0].collections[0].usage; + }, device_id); + cur_dev->interface_number = 0; + + input_report_count = MAIN_THREAD_EM_ASM_INT({ + return window._hidDeviceList[$0].collections[0].inputReports.length; + }, device_id); + + /* WebHID doesn't provide the bus type, so we have to guess it ourselves */ + if (input_report_count >= 5) { + cur_dev->bus_type = HID_API_BUS_BLUETOOTH; + } else { + cur_dev->bus_type = HID_API_BUS_USB; + } + + cur_dev->next = NULL; + +#ifdef HIDAPI_WEBHID_DEBUG + printf("HIDAPI: New device found: %d %d\n", cur_dev->vendor_id, cur_dev->product_id); +#endif +end: + return root; +} + +EM_ASYNC_JS(int, hid_js_get_device_count, (), { + window._hidDeviceList = await navigator.hid.getDevices(); + return window._hidDeviceList.length; +}); + +struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + int device_count; + struct hid_device_info *root = NULL; /* return object */ + struct hid_device_info *cur_dev = NULL; + + if (hid_init() < 0) { + return NULL; + } + + device_count = hid_js_get_device_count(); + +#ifdef HIDAPI_WEBHID_DEBUG + printf("hid_enumerate, device_count=%d\n", device_count); +#endif + + int device_id; + for (device_id = 0; device_id < device_count; device_id++) { + struct hid_device_info * tmp; + /* TODO: handle vendor_id and product_id */ + tmp = create_device_info_for_device(device_id); + + if (tmp) { + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + + /* move the pointer to the tail of returned list */ + while (cur_dev->next != NULL) { + cur_dev = cur_dev->next; + } + } + } + + return root; +} + +void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) +{ + struct hid_device_info *d = devs; + while (d) { + struct hid_device_info *next = d->next; + free(d->path); + free(d->product_string); + free(d); + d = next; + } +} + +hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) +{ + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device *handle = NULL; + + /* register_global_error: global error is reset by hid_enumerate/hid_init */ + devs = hid_enumerate(vendor_id, product_id); + if (devs == NULL) { + /* register_global_error: global error is already set by hid_enumerate */ + return NULL; + } + + cur_dev = devs; + while (cur_dev) { + if (cur_dev->vendor_id == vendor_id && + cur_dev->product_id == product_id) { + if (serial_number) { + if (wcscmp(serial_number, cur_dev->serial_number) == 0) { + path_to_open = cur_dev->path; + break; + } + } else { + path_to_open = cur_dev->path; + break; + } + } + cur_dev = cur_dev->next; + } + + if (path_to_open) { + /* Open the device */ + handle = hid_open_path(path_to_open); + } + + hid_free_enumeration(devs); + + return handle; +} + +EM_ASYNC_JS(void, hid_js_open, (int device_id, hid_device *dev, unsigned char **last_report_out, size_t *last_report_length_out), { + let device = window._hidDeviceList[device_id]; + if (device) { + try { + await device.open(); + device.addEventListener("inputreport", function (event) { + const { data, device, reportId } = event; + + let dataLength = data['byteLength']+1; + let pointer = _malloc(dataLength); + HEAPU8[pointer] = reportId; + for (let i = 0; i < data['byteLength']; i++) { + HEAPU8[pointer + i + 1] = data['getUint8'](i); + } + + if (HEAP32[last_report_out >> 2] != 0) { + _free(HEAP32[last_report_out >> 2]); + } + HEAP32[last_report_out >> 2] = pointer; + HEAP32[last_report_length_out >> 2] = dataLength; + }); + } catch (e) { + // Pass? + } + } +}); + +hid_device * HID_API_EXPORT hid_open_path(const char *path) +{ + hid_device *dev = NULL; + int device_id = 0; + + if (hid_init() < 0) { + return NULL; + } + /* register_global_error: global error is reset by hid_init */ + +#ifdef HIDAPI_WEBHID_DEBUG + printf("hid_open_path: %s\n", path); +#endif + dev = new_hid_device(); + if (!dev) { +#ifdef HIDAPI_WEBHID_DEBUG + printf("hid_open_path: no memory\n"); +#endif + return NULL; + } + + if (sscanf(path, "hid%d", &device_id) != 1) { + free(dev); +#ifdef HIDAPI_WEBHID_DEBUG + printf("hid_open_path: invalid path\n"); +#endif + return NULL; + } + + dev->device_id = device_id; + hid_js_open(device_id, dev, &dev->last_report, &dev->last_report_length); + + return dev; +} + +EM_ASYNC_JS(int, hid_js_write, (int device_id, int report_id, const unsigned char *data, size_t length), { + let device = window._hidDeviceList[device_id]; + if (device) { + let dataArray = new Uint8Array(length); + for (let i = 0; i < length; i++) { + dataArray[i] = HEAPU8[data+i]; + } + await device.sendReport(report_id, dataArray); + } +}); + +int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + if (!webhid_supported || length < 1) + return 0; + hid_js_write(dev->device_id, data[0], data+1, length-1); + return length; +} + +EM_ASYNC_JS(int, hid_js_read_timeout, (int device_id, unsigned char *data, size_t length), { + let device = window._hidDeviceList[device_id]; + if (device) { + let [report_id, dataView] = await new Promise(function(resolve, reject) { + device.addEventListener("inputreport", function (event) { + const { data, device, report_id } = event; + resolve([report_id, data]); // done + }, {once: true}); + }); + HEAPU8[data] = report_id; + for (let i = 0; i < dataView['byteLength'] && i < (length-1); i++) { + HEAPU8[data + i + 1] = dataView['getUint8'](i); + } + return dataView['byteLength']+1; + } + return 0; +}); + +int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) +{ + /* TODO: timeout */ + if (!webhid_supported || length < 1) + return -1; + if (milliseconds == 0) { + if (dev->last_report) { + size_t return_size = length; + if (dev->last_report_length < length) { + return_size = dev->last_report_length; + } + memcpy(data, dev->last_report, return_size); + free(dev->last_report); + dev->last_report = NULL; + return return_size; + } else { + return 0; + } + } + return hid_js_read_timeout(dev->device_id, data, length); +} + +int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + /* TODO: blocking */ + return hid_read_timeout(dev, data, length, -1); +} + +int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) +{ + /* TODO */ + return 0; +} + +int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + return 0; +} + +EM_ASYNC_JS(void, hid_js_get_feature_report, (int device_id, int report_id, unsigned char *data, size_t length), { + let device = window._hidDeviceList[device_id]; + if (device) { + let dataView = await device['receiveFeatureReport'](report_id); + for (let i = 0; i < dataView['byteLength']; i++) { + HEAPU8[data + i] = dataView['getUint8'](i); + } + } +}); + +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + if (!webhid_supported || length < 1) + return -1; + int report_id = (int)data[0]; + hid_js_get_feature_report(dev->device_id, report_id, data, length); + return 0; +} + +int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) +{ + return 0; +} + +EM_ASYNC_JS(int, hid_js_close, (int device_id), { + let device = window._hidDeviceList[device_id]; + if (device) { + await device.close(); + } +}); + +void HID_API_EXPORT hid_close(hid_device *dev) +{ + if (!dev) + return; + + if (dev->last_report) { + free(dev->last_report); + } + + hid_js_close(dev->device_id); + hid_free_enumeration(dev->device_info); + + free(dev); +} + +int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return 0; +} + +int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return 0; +} + +int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return 0; +} + +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) +{ + return NULL; +} + +int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) +{ + return -1; +} + +int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) +{ + return 0; +} + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + return L""; +} \ No newline at end of file diff --git a/src/joystick/emscripten/SDL_sysjoystick.c b/src/joystick/emscripten/SDL_sysjoystick.c index 00a4c0bbba..34f38c686a 100644 --- a/src/joystick/emscripten/SDL_sysjoystick.c +++ b/src/joystick/emscripten/SDL_sysjoystick.c @@ -28,6 +28,7 @@ #include "SDL_sysjoystick_c.h" #include "../SDL_joystick_c.h" #include "../usb_ids.h" +#include "../hidapi/SDL_hidapijoystick_c.h" static SDL_joylist_item *JoystickByIndex(int index); @@ -120,6 +121,61 @@ static int SDL_GetEmscriptenOSID() }); } +static EM_BOOL Emscripten_JoyStickDisconnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); + +#ifdef SDL_JOYSTICK_HIDAPI + +static void SDL_WebHID_DisconnectEmscriptenGamepad(int device_index) +{ + int rc; + EmscriptenGamepadEvent gamepadState; + rc = emscripten_get_gamepad_status(device_index, &gamepadState); + if (rc == EMSCRIPTEN_RESULT_SUCCESS) { + Emscripten_JoyStickDisconnected(EMSCRIPTEN_EVENT_GAMEPADDISCONNECTED, + &gamepadState, + NULL); + } +} + +static void SDL_RequestWebHIDDevice(Uint16 vendor, Uint16 product, int device_index) +{ + Uint16 product2 = 0; + if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) { + product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT; + product2 = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT; + } + MAIN_THREAD_EM_ASM({ + function timeout(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + if ("hid" in navigator) { + async function handler() { + while (true) { + try { + let devices = await navigator["hid"]["requestDevice"]({ "filters": [ { "vendorId": $0, "productId": $1, } ]}); + let device_length = devices["length"]; + if ($4) { // product2 + devices = await navigator["hid"]["requestDevice"]({ "filters": [ { "vendorId": $0, "productId": $4, } ]}); + device_length += devices["length"]; + } + if (devices["length"]) { + dynCall("vi", $2, [$3]); + } + return; + } catch(e) { + // Exception, most likely because the user hasn't interacted with the page yet. + // Let's wait until they do, hopefully. + await timeout(500); + } + } + } + handler(); + } + }, vendor, product, SDL_WebHID_DisconnectEmscriptenGamepad, device_index, product2); +} +#endif + static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { SDL_joylist_item *item; @@ -148,6 +204,12 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep product = SDL_GetEmscriptenJoystickProduct(gamepadEvent->index); is_xinput = SDL_IsEmscriptenJoystickXInput(gamepadEvent->index); +#ifdef SDL_JOYSTICK_HIDAPI + if (HIDAPI_IsDeviceSupported(vendor, product, 0, "")) { + SDL_RequestWebHIDDevice(vendor, product, gamepadEvent->index); + } +#endif + // Use a generic VID/PID representing an XInput controller if (!vendor && !product && is_xinput) { vendor = USB_VENDOR_MICROSOFT; @@ -287,7 +349,7 @@ done: return 1; } -static EM_BOOL Emscripten_JoyStickDisconnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) +EM_BOOL Emscripten_JoyStickDisconnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { SDL_joylist_item *item = SDL_joylist; SDL_joylist_item *prev = NULL; diff --git a/src/joystick/hidapi/SDL_hidapi_rumble.c b/src/joystick/hidapi/SDL_hidapi_rumble.c index 7747b9dd4b..4afbb70394 100644 --- a/src/joystick/hidapi/SDL_hidapi_rumble.c +++ b/src/joystick/hidapi/SDL_hidapi_rumble.c @@ -49,12 +49,15 @@ typedef struct SDL_HIDAPI_RumbleContext SDL_HIDAPI_RumbleRequest *requests_tail; } SDL_HIDAPI_RumbleContext; +#ifndef SDL_THREADS_DISABLED #ifndef SDL_THREAD_SAFETY_ANALYSIS static #endif SDL_Mutex *SDL_HIDAPI_rumble_lock; +#endif static SDL_HIDAPI_RumbleContext rumble_context SDL_GUARDED_BY(SDL_HIDAPI_rumble_lock); +#ifndef SDL_THREADS_DISABLED static int SDLCALL SDL_HIDAPI_RumbleThread(void *data) { SDL_HIDAPI_RumbleContext *ctx = (SDL_HIDAPI_RumbleContext *)data; @@ -161,9 +164,11 @@ static bool SDL_HIDAPI_StartRumbleThread(SDL_HIDAPI_RumbleContext *ctx) } return true; } +#endif bool SDL_HIDAPI_LockRumble(void) { +#ifndef SDL_THREADS_DISABLED SDL_HIDAPI_RumbleContext *ctx = &rumble_context; if (SDL_CompareAndSwapAtomicInt(&ctx->initialized, false, true)) { @@ -173,11 +178,13 @@ bool SDL_HIDAPI_LockRumble(void) } SDL_LockMutex(SDL_HIDAPI_rumble_lock); +#endif return true; } bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size) { +#ifndef SDL_THREADS_DISABLED SDL_HIDAPI_RumbleContext *ctx = &rumble_context; SDL_HIDAPI_RumbleRequest *request, *found; @@ -193,6 +200,7 @@ bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, *maximum_size = sizeof(found->data); return true; } +#endif return false; } @@ -212,6 +220,7 @@ int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const return -1; } +#ifndef SDL_THREADS_DISABLED request = (SDL_HIDAPI_RumbleRequest *)SDL_calloc(1, sizeof(*request)); if (!request) { SDL_HIDAPI_UnlockRumble(); @@ -234,6 +243,9 @@ int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const // Make sure we unlock before posting the semaphore so the rumble thread can run immediately SDL_HIDAPI_UnlockRumble(); +#else + SDL_hid_write(device->dev, data, size); +#endif SDL_SignalSemaphore(ctx->request_sem); @@ -242,7 +254,9 @@ int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const void SDL_HIDAPI_UnlockRumble(void) { +#ifndef SDL_THREADS_DISABLED SDL_UnlockMutex(SDL_HIDAPI_rumble_lock); +#endif } int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size) @@ -273,11 +287,13 @@ int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size void SDL_HIDAPI_QuitRumble(void) { +#ifndef SDL_THREADS_DISABLED SDL_HIDAPI_RumbleContext *ctx = &rumble_context; if (SDL_GetAtomicInt(&ctx->running)) { SDL_HIDAPI_StopRumbleThread(ctx); } +#endif } #endif // SDL_JOYSTICK_HIDAPI diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index 2ab43b2c83..621924fc26 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -343,7 +343,7 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U return type; } -static bool HIDAPI_IsDeviceSupported(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +bool HIDAPI_IsDeviceSupported(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) { int i; SDL_GamepadType type = SDL_GetJoystickGameControllerProtocol(name, vendor_id, product_id, -1, 0, 0, 0); @@ -676,6 +676,10 @@ static bool HIDAPI_SerialIsEmpty(SDL_HIDAPI_Device *device) void HIDAPI_SetDeviceSerial(SDL_HIDAPI_Device *device, const char *serial) { +#ifdef SDL_PLATFORM_EMSCRIPTEN + // Don't include the serial number for the web to decrease the fingerprinting surface + serial = NULL; +#endif if (serial && *serial && (!device->serial || SDL_strcmp(serial, device->serial) != 0)) { SDL_free(device->serial); device->serial = SDL_strdup(serial); @@ -700,6 +704,10 @@ static int wcstrcmp(const wchar_t *str1, const char *str2) static void HIDAPI_SetDeviceSerialW(SDL_HIDAPI_Device *device, const wchar_t *serial) { +#ifdef SDL_PLATFORM_EMSCRIPTEN + // Don't include the serial number for the web to decrease the fingerprinting surface + serial = NULL; +#endif if (serial && *serial && (!device->serial || wcstrcmp(serial, device->serial) != 0)) { SDL_free(device->serial); device->serial = HIDAPI_ConvertString(serial); diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index 59c62f6fe5..c30102bea0 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -63,8 +63,12 @@ #if defined(SDL_PLATFORM_ANDROID) || \ defined(SDL_PLATFORM_IOS) || \ defined(SDL_PLATFORM_TVOS) || \ - defined(SDL_PLATFORM_VISIONOS) -// On Android, HIDAPI prompts for permissions and acquires exclusive access to the device, and on Apple mobile platforms it doesn't do anything except for handling Bluetooth Steam Controllers, so we'll leave it off by default. + defined(SDL_PLATFORM_VISIONOS) || \ + defined(SDL_PLATFORM_EMSCRIPTEN) +// On Android, HIDAPI prompts for permissions and acquires exclusive access to the device, +// on Emscripten, it also prompts for permissions and it's not available in some browsers, +// and on Apple mobile platforms it doesn't do anything except for handling Bluetooth Steam Controllers, +// so we'll leave it off by default. #define SDL_HIDAPI_DEFAULT false #else #define SDL_HIDAPI_DEFAULT true @@ -180,6 +184,8 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverZUIKI; (((Uint32)(C)) << 16) | \ (((Uint32)(D)) << 24)) +extern bool HIDAPI_IsDeviceSupported(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name); + // Return true if a HID device is present and supported as a joystick of the given type extern bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type);