From 7490471796f287cb284b21ec2643af98e9982b53 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Sun, 4 May 2025 15:01:25 -0500 Subject: [PATCH 01/83] cpuinfo: Use auxv for AltiVec on Linux if possible The SIGILL handler is not very reliable and can cause crashes. Linux provides the CPU's AltiVec support status in getauxval. --- src/cpuinfo/SDL_cpuinfo.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cpuinfo/SDL_cpuinfo.c b/src/cpuinfo/SDL_cpuinfo.c index 81d1e914ce..a8feec846f 100644 --- a/src/cpuinfo/SDL_cpuinfo.c +++ b/src/cpuinfo/SDL_cpuinfo.c @@ -115,7 +115,7 @@ #define CPU_CFG2_LSX (1 << 6) #define CPU_CFG2_LASX (1 << 7) -#if defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_OPENBSD) && !defined(SDL_PLATFORM_FREEBSD) +#if defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_OPENBSD) && !defined(SDL_PLATFORM_FREEBSD) && (defined(SDL_PLATFORM_LINUX) && !defined(HAVE_GETAUXVAL)) /* This is the brute force way of detecting instruction sets... the idea is borrowed from the libmpeg2 library - thanks! */ @@ -344,6 +344,8 @@ static int CPU_haveAltiVec(void) elf_aux_info(AT_HWCAP, &cpufeatures, sizeof(cpufeatures)); altivec = cpufeatures & PPC_FEATURE_HAS_ALTIVEC; return altivec; +#elif defined(SDL_PLATFORM_LINUX) && defined(__powerpc__) && defined(HAVE_GETAUXVAL) + altivec = getauxval(AT_HWCAP) & PPC_FEATURE_HAS_ALTIVEC; #elif defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) void (*handler)(int sig); handler = signal(SIGILL, illegal_instruction); From c91f9f6968b358500df92c8bd1a0555efca5819b Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Mon, 5 May 2025 10:34:00 -0400 Subject: [PATCH 02/83] x11: #ifdef the XRandR path in the message box code The runtime check isn't sufficient as the functions are undefined if built without XRandR. --- src/video/x11/SDL_x11messagebox.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/video/x11/SDL_x11messagebox.c b/src/video/x11/SDL_x11messagebox.c index 34b7260ebb..ab2174e02a 100644 --- a/src/video/x11/SDL_x11messagebox.c +++ b/src/video/x11/SDL_x11messagebox.c @@ -425,10 +425,12 @@ static bool X11_MessageBoxCreateWindow(SDL_MessageBoxDataX11 *data) Display *display = data->display; SDL_WindowData *windowdata = NULL; const SDL_MessageBoxData *messageboxdata = data->messageboxdata; +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR #ifdef XRANDR_DISABLED_BY_DEFAULT const bool use_xrandr_by_default = false; #else const bool use_xrandr_by_default = true; +#endif #endif if (messageboxdata->window) { @@ -502,12 +504,16 @@ static bool X11_MessageBoxCreateWindow(SDL_MessageBoxDataX11 *data) const SDL_DisplayData *dpydata = dpy->internal; x = dpydata->x + ((dpy->current_mode->w - data->dialog_width) / 2); y = dpydata->y + ((dpy->current_mode->h - data->dialog_height) / 3); - } else if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, use_xrandr_by_default)) { + } +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + else if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, use_xrandr_by_default)) { XRRScreenResources *screen = X11_XRRGetScreenResourcesCurrent(display, DefaultRootWindow(display)); XRRCrtcInfo *crtc_info = X11_XRRGetCrtcInfo(display, screen, screen->crtcs[0]); x = (crtc_info->width - data->dialog_width) / 2; y = (crtc_info->height - data->dialog_height) / 3; - } else { + } +#endif + else { // oh well. This will misposition on a multi-head setup. Init first next time. x = (DisplayWidth(display, data->screen) - data->dialog_width) / 2; y = (DisplayHeight(display, data->screen) - data->dialog_height) / 3; From c6362b47887e231965c193dffeb902b56f04a561 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Mon, 5 May 2025 11:08:15 -0400 Subject: [PATCH 03/83] tests: Revert some leftover testing code --- src/test/SDL_test_common.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index 2622b02af9..5f1ab6b920 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -2511,13 +2511,7 @@ SDL_AppResult SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const /* Ctrl-G toggle mouse grab */ SDL_Window *window = SDL_GetWindowFromEvent(event); if (window) { - if (SDL_RectEmpty(SDL_GetWindowMouseRect(window))) { - SDL_Rect r = { 10, 10, 200, 200}; - SDL_SetWindowMouseRect(window, &r); - } else { - SDL_SetWindowMouseRect(window, NULL); - } - //SDL_SetWindowMouseGrab(window, !SDL_GetWindowMouseGrab(window)); + SDL_SetWindowMouseGrab(window, !SDL_GetWindowMouseGrab(window)); } } break; From 5bd886519be5fd40e7e2af2c33cb7d68d27cc3e6 Mon Sep 17 00:00:00 2001 From: SDL Wiki Bot Date: Mon, 5 May 2025 15:10:37 +0000 Subject: [PATCH 04/83] Sync SDL3 wiki -> header [ci skip] --- include/SDL3/SDL_gpu.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index c4e96ae2a2..00c7abacc2 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -4013,7 +4013,9 @@ extern SDL_DECLSPEC SDL_GPUTextureFormat SDLCALL SDL_GetGPUSwapchainTextureForma * buffer used to acquire it. * * This function will fill the swapchain texture handle with NULL if too many - * frames are in flight. This is not an error. + * frames are in flight. This is not an error. This NULL pointer should not be + * passed back into SDL. Instead, it should be considered as an indication to + * wait until the swapchain is available. * * If you use this function, it is possible to create a situation where many * command buffers are allocated while the rendering context waits for the GPU From 85d2345bd8c2105c3495f4fe43473664d40d27b1 Mon Sep 17 00:00:00 2001 From: SDL Wiki Bot Date: Mon, 5 May 2025 15:21:20 +0000 Subject: [PATCH 05/83] Sync SDL3 wiki -> header [ci skip] --- include/SDL3/SDL_gpu.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 00c7abacc2..70b7def89a 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2986,6 +2986,9 @@ extern SDL_DECLSPEC SDL_GPUCommandBuffer * SDLCALL SDL_AcquireGPUCommandBuffer( * terms this means you must ensure that vec3 and vec4 fields are 16-byte * aligned. * + * For detailed information about accessing uniform data from a shader, please + * refer to SDL_CreateGPUShader. + * * \param command_buffer a command buffer. * \param slot_index the vertex uniform slot to push data to. * \param data client data to write. From 1abac3ccc360d646d26e41790e6b54c4460e440f Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Mon, 5 May 2025 10:52:42 -0400 Subject: [PATCH 06/83] Revert "x11: Better handle XInput2 mouse tracking outside the window" This reverts commit 8c733d1f7bdb03b2082028349e97137c0bdea13b. --- src/video/x11/SDL_x11events.c | 39 ++++++---------------------------- src/video/x11/SDL_x11window.h | 1 - src/video/x11/SDL_x11xinput2.c | 18 +++++++--------- 3 files changed, 15 insertions(+), 43 deletions(-) diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 706ce4d9e2..dcc6c878c6 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -492,17 +492,7 @@ static void X11_DispatchFocusOut(SDL_VideoDevice *_this, SDL_WindowData *data) /* If another window has already processed a focus in, then don't try to * remove focus here. Doing so will incorrectly remove focus from that * window, and the focus lost event for this window will have already - * been dispatched anyway. - */ - if (data->tracking_mouse_outside_window && data->window == SDL_GetMouseFocus()) { - // If tracking the pointer and keyboard focus is lost, raise all buttons and relinquish mouse focus. - SDL_SendMouseButton(0, data->window, SDL_GLOBAL_MOUSE_ID, SDL_BUTTON_LEFT, false); - SDL_SendMouseButton(0, data->window, SDL_GLOBAL_MOUSE_ID, SDL_BUTTON_MIDDLE, false); - SDL_SendMouseButton(0, data->window, SDL_GLOBAL_MOUSE_ID, SDL_BUTTON_RIGHT, false); - SDL_SendMouseButton(0, data->window, SDL_GLOBAL_MOUSE_ID, SDL_BUTTON_X1, false); - SDL_SendMouseButton(0, data->window, SDL_GLOBAL_MOUSE_ID, SDL_BUTTON_X2, false); - SDL_SetMouseFocus(NULL); - } + * been dispatched anyway. */ if (data->window == SDL_GetKeyboardFocus()) { SDL_SetKeyboardFocus(NULL); } @@ -1084,16 +1074,6 @@ void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *windowdata, // see explanation at case ButtonPress button -= (8 - SDL_BUTTON_X1); } - - /* If the mouse is captured and all buttons are now released, clear the capture - * flag so the focus will be cleared if the mouse is outside the window. - */ - if ((window->flags & SDL_WINDOW_MOUSE_CAPTURE) && - !(SDL_GetMouseState(NULL, NULL) & ~SDL_BUTTON_MASK(button))) { - window->flags &= ~SDL_WINDOW_MOUSE_CAPTURE; - windowdata->tracking_mouse_outside_window = false; - } - SDL_SendMouseButton(timestamp, window, mouseID, button, false); } } @@ -1339,8 +1319,6 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) SDL_Log("Mode: NotifyUngrab"); } #endif - data->tracking_mouse_outside_window = false; - SDL_SetMouseFocus(data->window); mouse->last_x = xevent->xcrossing.x; @@ -1387,17 +1365,14 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) if (xevent->xcrossing.mode != NotifyGrab && xevent->xcrossing.mode != NotifyUngrab && xevent->xcrossing.detail != NotifyInferior) { - if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { - /* In order for interaction with the window decorations and menu to work properly - on Mutter, we need to ungrab the keyboard when the mouse leaves. */ - if (!(data->window->flags & SDL_WINDOW_FULLSCREEN)) { - X11_SetWindowKeyboardGrab(_this, data->window, false); - } - SDL_SetMouseFocus(NULL); - } else { - data->tracking_mouse_outside_window = true; + /* In order for interaction with the window decorations and menu to work properly + on Mutter, we need to ungrab the keyboard when the mouse leaves. */ + if (!(data->window->flags & SDL_WINDOW_FULLSCREEN)) { + X11_SetWindowKeyboardGrab(_this, data->window, false); } + + SDL_SetMouseFocus(NULL); } } break; diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h index e7ca1ce4c6..893334075f 100644 --- a/src/video/x11/SDL_x11window.h +++ b/src/video/x11/SDL_x11window.h @@ -117,7 +117,6 @@ struct SDL_WindowData bool fullscreen_borders_forced_on; bool was_shown; bool emit_size_move_after_property_notify; - bool tracking_mouse_outside_window; SDL_HitTestResult hit_test_result; XPoint xim_spot; diff --git a/src/video/x11/SDL_x11xinput2.c b/src/video/x11/SDL_x11xinput2.c index e475710a2d..afe4a7c85b 100644 --- a/src/video/x11/SDL_x11xinput2.c +++ b/src/video/x11/SDL_x11xinput2.c @@ -467,17 +467,15 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) SDL_SendPenAxis(0, pen->pen, window, (SDL_PenAxis) i, axes[i]); } } - } else { + } else if (!pointer_emulated && xev->deviceid == videodata->xinput_master_pointer_device) { + // Use the master device for non-relative motion, as the slave devices can seemingly lag behind. SDL_Mouse *mouse = SDL_GetMouse(); - SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); - if (!mouse->relative_mode && !pointer_emulated && window && - (xev->deviceid == videodata->xinput_master_pointer_device || window->internal->tracking_mouse_outside_window)) { - /* Use the master device for non-relative motion, as the slave devices can seemingly lag behind, unless - * tracking the mouse outside the window, in which case the slave devices deliver coordinates, while the - * master does not. - */ - X11_ProcessHitTest(_this, window->internal, (float)xev->event_x, (float)xev->event_y, false); - SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, (float)xev->event_x, (float)xev->event_y); + if (!mouse->relative_mode) { + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + if (window) { + X11_ProcessHitTest(_this, window->internal, (float)xev->event_x, (float)xev->event_y, false); + SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, (float)xev->event_x, (float)xev->event_y); + } } } } break; From 33e5f4885aeb20f74730d8abfcb4c5fb230ba406 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Mon, 5 May 2025 10:55:26 -0400 Subject: [PATCH 07/83] x11: Don't update grab on enter when the mouse is captured The xserver will still send EnterNotify events while the pointer is captured, and the grab shouldn't be updated in these cases, as it will cause the capture to be lost. --- src/video/x11/SDL_x11events.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index dcc6c878c6..a4e5d9caf6 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -1338,8 +1338,10 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) SDL_SendMouseMotion(0, data->window, SDL_GLOBAL_MOUSE_ID, false, (float)xevent->xcrossing.x, (float)xevent->xcrossing.y); } - // We ungrab in LeaveNotify, so we may need to grab again here - SDL_UpdateWindowGrab(data->window); + // We ungrab in LeaveNotify, so we may need to grab again here, but not if captured, as the capture can be lost. + if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { + SDL_UpdateWindowGrab(data->window); + } X11_ProcessHitTest(_this, data, mouse->last_x, mouse->last_y, true); } break; From f8c77908adf13bebfff2625cc81a433f7d6a4ce5 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Fri, 2 May 2025 10:19:57 -0700 Subject: [PATCH 08/83] Use motor sequence ID 0 in the HIDAPI GIP driver Using a 0 sequence number is always allowed and avoids having to synchronize sequence numbers with the controller (and potentially confusing other things talking to the controller) Also fixes occasional long running rumble at controller connection. --- src/joystick/hidapi/SDL_hidapi_gip.c | 39 ++++++++++------------------ 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c index e471267c38..81a51e10fb 100644 --- a/src/joystick/hidapi/SDL_hidapi_gip.c +++ b/src/joystick/hidapi/SDL_hidapi_gip.c @@ -393,7 +393,7 @@ typedef struct GIP_Metadata Uint16 version_minor; GIP_DeviceMetadata device; - + Uint8 num_messages; GIP_MessageMetadata *message_metadata; } GIP_Metadata; @@ -575,6 +575,7 @@ static bool GIP_SupportsVendorMessage(GIP_Device *device, Uint8 command, bool up static Uint8 GIP_SequenceNext(GIP_Device *device, Uint8 command, bool system) { Uint8 seq; + if (system) { switch (command) { case GIP_CMD_SECURITY: @@ -603,6 +604,11 @@ static Uint8 GIP_SequenceNext(GIP_Device *device, Uint8 command, bool system) break; } } else { + if (command == GIP_CMD_DIRECT_MOTOR) { + // The motor sequence number is optional and always works with 0 + return 0; + } + seq = device->seq_vendor++; if (!seq) { seq = device->seq_vendor++; @@ -792,7 +798,7 @@ static bool GIP_ParseDeviceMetadata(GIP_Metadata *metadata, const Uint8 *bytes, for (i = 0; i < count; i++) { Uint8 message = bytes[buffer_offset + 1 + i]; - device->in_system_messages[message >> 5] |= 1u << (message & 0x1F); + device->in_system_messages[message >> 5] |= 1u << (message & 0x1F); } } @@ -809,7 +815,7 @@ static bool GIP_ParseDeviceMetadata(GIP_Metadata *metadata, const Uint8 *bytes, for (i = 0; i < count; i++) { Uint8 message = bytes[buffer_offset + 1 + i]; - device->out_system_messages[message >> 5] |= 1u << (message & 0x1F); + device->out_system_messages[message >> 5] |= 1u << (message & 0x1F); } } @@ -1122,7 +1128,7 @@ static bool GIP_SendInitSequence(GIP_Device *device) static bool GIP_EnsureMetadata(GIP_Device *device) { - + switch (device->got_metadata) { case GIP_METADATA_GOT: case GIP_METADATA_FAKED: @@ -1147,8 +1153,6 @@ static bool GIP_EnsureMetadata(GIP_Device *device) static bool GIP_SetMetadataDefaults(GIP_Device *device) { - int seq; - /* Some decent default settings */ device->features |= GIP_FEATURE_MOTOR_CONTROL; device->device_type = GIP_TYPE_GAMEPAD; @@ -1164,23 +1168,6 @@ static bool GIP_SetMetadataDefaults(GIP_Device *device) GIP_SendQueryFirmware(device, 2); } - if (device->features & GIP_FEATURE_MOTOR_CONTROL) { - for (seq = 1; seq < 0x100; seq++) { - Uint8 message[9] = {0}; - - /* Try all sequence numbers to reset it to 1 */ - GIP_SendRawMessage(device, - GIP_CMD_DIRECT_MOTOR, - 0, - (Uint8) seq, - message, - sizeof(message), - true, - NULL, - NULL); - } - } - device->got_metadata = GIP_METADATA_FAKED; device->hello_deadline = 0; return HIDAPI_JoystickConnected(device->device, NULL); @@ -1512,7 +1499,7 @@ static bool GIP_HandleCommandGuideButtonStatus( if (!joystick) { return false; } - if (bytes[1] == VK_LWIN) { + if (bytes[1] == VK_LWIN) { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (bytes[0] & 0x01) != 0); } @@ -1815,7 +1802,7 @@ static bool GIP_HandleLLInputReport( (bytes[device->paddle_offset] & 0x08) != 0); } } - + if ((device->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) && num_bytes >= 32) { int function_map_offset = -1; if (device->features & GIP_FEATURE_DYNAMIC_LATENCY_INPUT) { @@ -2057,7 +2044,7 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt device->fragment_data = NULL; } device->fragment_message = 0; - } + } fragment_offset += header.length; device->fragment_offset = (Uint16) fragment_offset; } From d357aa29a3cbeb12745cfb1428098e228cb5bf02 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Mon, 5 May 2025 15:52:28 -0700 Subject: [PATCH 09/83] Fixed initializing the OpenVR driver --- src/video/openvr/SDL_openvrvideo.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/video/openvr/SDL_openvrvideo.c b/src/video/openvr/SDL_openvrvideo.c index a89467cc1b..54e44ba43b 100644 --- a/src/video/openvr/SDL_openvrvideo.c +++ b/src/video/openvr/SDL_openvrvideo.c @@ -22,7 +22,9 @@ #ifdef SDL_VIDEO_DRIVER_OPENVR +#if 0 #define DEBUG_OPENVR +#endif #include "../../events/SDL_mouse_c.h" #include "../../events/SDL_keyboard_c.h" @@ -445,7 +447,7 @@ static void OPENVR_VirtualControllerUpdate(void *userdata) static bool OPENVR_SetupJoystickBasedOnLoadedActionManifest(SDL_VideoData * videodata) { SDL_VirtualJoystickDesc desc; - int virtual_index; + SDL_JoystickID virtual_id; EVRInputError e = 0; @@ -537,9 +539,9 @@ static bool OPENVR_SetupJoystickBasedOnLoadedActionManifest(SDL_VideoData * vide desc.RumbleTriggers = OPENVR_VirtualControllerRumbleTriggers; desc.Update = OPENVR_VirtualControllerUpdate; desc.userdata = videodata; - virtual_index = SDL_AttachVirtualJoystick(&desc); + virtual_id = SDL_AttachVirtualJoystick(&desc); - if (virtual_index < 0) { + if (!virtual_id) { return SDL_SetError("OPENVR: Couldn't open virtual joystick device: %s", SDL_GetError()); } else { videodata->virtual_joystick = SDL_OpenJoystick(virtual_index); @@ -552,7 +554,7 @@ static bool OPENVR_SetupJoystickBasedOnLoadedActionManifest(SDL_VideoData * vide SDL_Log("Loaded virtual joystick with %d buttons and %d axes", videodata->input_action_handles_buttons_count, videodata->input_action_handles_axes_count); #endif - return false; + return true; } static bool OPENVR_InitializeOverlay(SDL_VideoDevice *_this,SDL_Window *window) From 92a5417a981665ae90904bfd1f9760440b2de49d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 5 May 2025 23:34:20 -0400 Subject: [PATCH 10/83] docs: Tweak some minor things to keep wikiheaders happy. You can't have two sections with the same name (so it can generate unique page anchors), so fix one of these, and tweak another thing that _isn't_ a header but wikiheader's quick-and-dirty parser thinks is one. --- docs/README-cmake.md | 4 ++-- docs/README-emscripten.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/README-cmake.md b/docs/README-cmake.md index a87b0daf0d..44423ab356 100644 --- a/docs/README-cmake.md +++ b/docs/README-cmake.md @@ -157,7 +157,7 @@ flags to the compiler. - Use [`CMAKE_EXE_LINKER_FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_EXE_LINKER_FLAGS.html) to pass extra option to the linker for executables. - Use [`CMAKE_SHARED_LINKER_FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_SHARED_LINKER_FLAGS.html) to pass extra options to the linker for shared libraries. -#### Examples +#### Compile Options Examples - build a SDL library optimized for (more) modern x64 microprocessor architectures. @@ -240,7 +240,7 @@ Append with a version number to target a specific SDK revision: e.g. `iphoneos12 CMake documentation: [link](https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_SYSROOT.html) -#### Examples +#### Apple Examples - for macOS, building a dylib and/or static library for x86_64 and arm64: diff --git a/docs/README-emscripten.md b/docs/README-emscripten.md index 2b8146893a..c1d16b8fe9 100644 --- a/docs/README-emscripten.md +++ b/docs/README-emscripten.md @@ -230,7 +230,7 @@ tools. mkdir build cd build emcmake cmake .. -# you can also do `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. +# you can also try `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. emmake make -j4 ``` From 2c97a48c512c4af9ce1b4934151f66750ab92240 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 5 May 2025 23:36:08 -0400 Subject: [PATCH 11/83] wikiheaders: READMEs go in the base dir of the wiki now. Reference PR #12529. (and several other issues.) --- build-scripts/wikiheaders.pl | 37 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/build-scripts/wikiheaders.pl b/build-scripts/wikiheaders.pl index 019304e9b0..c9ccd0ba25 100755 --- a/build-scripts/wikiheaders.pl +++ b/build-scripts/wikiheaders.pl @@ -43,7 +43,6 @@ my $wikiurl = 'https://wiki.libsdl.org'; my $bugreporturl = 'https://github.com/libsdl-org/sdlwiki/issues/new'; my $srcpath = undef; my $wikipath = undef; -my $wikireadmesubdir = 'README'; my $warn_about_missing = 0; my $copy_direction = 0; my $optionsfname = undef; @@ -1033,7 +1032,6 @@ sub generate_quickref { my $incpath = "$srcpath"; $incpath .= "/$incsubdir" if $incsubdir ne ''; -my $wikireadmepath = "$wikipath/$wikireadmesubdir"; my $readmepath = undef; if (defined $readmesubdir) { $readmepath = "$srcpath/$readmesubdir"; @@ -2082,18 +2080,15 @@ if ($copy_direction == 1) { # --copy-to-headers } if (defined $readmepath) { - if ( -d $wikireadmepath ) { - mkdir($readmepath); # just in case - opendir(DH, $wikireadmepath) or die("Can't opendir '$wikireadmepath': $!\n"); - while (readdir(DH)) { - my $dent = $_; - if ($dent =~ /\A(.*?)\.md\Z/) { # we only bridge Markdown files here. - next if $1 eq 'FrontPage'; - filecopy("$wikireadmepath/$dent", "$readmepath/README-$dent", "\n"); - } + mkdir($readmepath); # just in case + opendir(DH, $wikipath) or die("Can't opendir '$wikipath': $!\n"); + while (readdir(DH)) { + my $dent = $_; + if ($dent =~ /\AREADME\-.*?\.md\Z/) { # we only bridge Markdown files here that start with "README-". + filecopy("$wikipath/$dent", "$readmepath/$dent", "\n"); } - closedir(DH); } + closedir(DH); } } elsif ($copy_direction == -1) { # --copy-to-wiki @@ -2698,31 +2693,27 @@ __EOF__ # Write out READMEs... if (defined $readmepath) { if ( -d $readmepath ) { - mkdir($wikireadmepath); # just in case + mkdir($wikipath); # just in case opendir(DH, $readmepath) or die("Can't opendir '$readmepath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; - if ($dent =~ /\AREADME\-(.*?\.md)\Z/) { # we only bridge Markdown files here. - my $wikifname = $1; - next if $wikifname eq 'FrontPage.md'; - filecopy("$readmepath/$dent", "$wikireadmepath/$wikifname", "\n"); + if ($dent =~ /\AREADME\-.*?\.md\Z/) { # we only bridge Markdown files here that start with "README-". + filecopy("$readmepath/$dent", "$wikipath/$dent", "\n"); } } closedir(DH); my @pages = (); - opendir(DH, $wikireadmepath) or die("Can't opendir '$wikireadmepath': $!\n"); + opendir(DH, $wikipath) or die("Can't opendir '$wikipath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; - if ($dent =~ /\A(.*?)\.(mediawiki|md)\Z/) { - my $wikiname = $1; - next if $wikiname eq 'FrontPage'; - push @pages, $wikiname; + if ($dent =~ /\AREADME\-.*?\.md\Z/) { + push @pages, $dent; } } closedir(DH); - open(FH, '>', "$wikireadmepath/FrontPage.md") or die("Can't open '$wikireadmepath/FrontPage.md': $!\n"); + open(FH, '>', "$wikipath/README.md") or die("Can't open '$wikipath/README.md': $!\n"); print FH "# All READMEs available here\n\n"; foreach (sort @pages) { my $wikiname = $_; From ad46394e82334016abd956e2ee7a7b50186ed356 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 5 May 2025 23:49:06 -0400 Subject: [PATCH 12/83] wikiheaders: README.md should be READMEs.md So it doesn't conflict with the old README directory. --- build-scripts/wikiheaders.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-scripts/wikiheaders.pl b/build-scripts/wikiheaders.pl index c9ccd0ba25..fa9407ebca 100755 --- a/build-scripts/wikiheaders.pl +++ b/build-scripts/wikiheaders.pl @@ -2713,7 +2713,7 @@ __EOF__ } closedir(DH); - open(FH, '>', "$wikipath/README.md") or die("Can't open '$wikipath/README.md': $!\n"); + open(FH, '>', "$wikipath/READMEs.md") or die("Can't open '$wikipath/READMEs.md': $!\n"); print FH "# All READMEs available here\n\n"; foreach (sort @pages) { my $wikiname = $_; From 06c2f9fcfc89577c48d33dfc6882545713ad19f6 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 5 May 2025 23:56:12 -0400 Subject: [PATCH 13/83] wikiheaders: Correct wiki README digest links. --- build-scripts/wikiheaders.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build-scripts/wikiheaders.pl b/build-scripts/wikiheaders.pl index fa9407ebca..e54c27bdd1 100755 --- a/build-scripts/wikiheaders.pl +++ b/build-scripts/wikiheaders.pl @@ -2707,8 +2707,8 @@ __EOF__ opendir(DH, $wikipath) or die("Can't opendir '$wikipath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; - if ($dent =~ /\AREADME\-.*?\.md\Z/) { - push @pages, $dent; + if ($dent =~ /\(AREADME\-.*?)\.md\Z/) { + push @pages, $1; } } closedir(DH); From a7344206e85c060da1f21391b50320f849f9f5d6 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 5 May 2025 23:58:59 -0400 Subject: [PATCH 14/83] wikiheaders: Patched to compile. (whoops.) --- build-scripts/wikiheaders.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-scripts/wikiheaders.pl b/build-scripts/wikiheaders.pl index e54c27bdd1..51b953dfde 100755 --- a/build-scripts/wikiheaders.pl +++ b/build-scripts/wikiheaders.pl @@ -2707,7 +2707,7 @@ __EOF__ opendir(DH, $wikipath) or die("Can't opendir '$wikipath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; - if ($dent =~ /\(AREADME\-.*?)\.md\Z/) { + if ($dent =~ /\A(README\-.*?)\.md\Z/) { push @pages, $1; } } From 71303b41bf89c4bb68dbe1bed12fd2ee1dc0dd41 Mon Sep 17 00:00:00 2001 From: SDL Wiki Bot Date: Tue, 6 May 2025 05:16:13 +0000 Subject: [PATCH 15/83] Sync SDL3 wiki -> header [ci skip] --- docs/README-documentation-rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README-documentation-rules.md b/docs/README-documentation-rules.md index 757a3c5fcc..793acdb336 100644 --- a/docs/README-documentation-rules.md +++ b/docs/README-documentation-rules.md @@ -340,7 +340,7 @@ through, header users can search for the function name. You might be reading this document on the wiki! Any `README-*.md` files in the docs directory are bridged to the wiki, so `docs/README-linux.md` lands -at https://wiki.libsdl.org/SDL3/README/linux ...these are just copied directly +at https://wiki.libsdl.org/SDL3/README-linux ...these are just copied directly without any further processing by wikiheaders, and changes go in both directions. From bbd973c8d2f34482decc0bbd44c7606a61d28ff5 Mon Sep 17 00:00:00 2001 From: SDL Wiki Bot Date: Tue, 6 May 2025 16:46:42 +0000 Subject: [PATCH 16/83] Sync SDL3 wiki -> header [ci skip] --- include/SDL3/SDL_events.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 0201834ec2..2094a598dd 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -781,7 +781,7 @@ typedef struct SDL_TouchFingerEvent } SDL_TouchFingerEvent; /** - * Pressure-sensitive pen proximity event structure (event.pmotion.*) + * Pressure-sensitive pen proximity event structure (event.pproximity.*) * * When a pen becomes visible to the system (it is close enough to a tablet, * etc), SDL will send an SDL_EVENT_PEN_PROXIMITY_IN event with the new pen's From 20f783532b536bd01d83548f20e396ec531bdb91 Mon Sep 17 00:00:00 2001 From: SDL Wiki Bot Date: Tue, 6 May 2025 17:21:29 +0000 Subject: [PATCH 17/83] Sync SDL3 wiki -> header [ci skip] --- docs/README-documentation-rules.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/README-documentation-rules.md b/docs/README-documentation-rules.md index 793acdb336..bf82be2a04 100644 --- a/docs/README-documentation-rules.md +++ b/docs/README-documentation-rules.md @@ -327,6 +327,16 @@ If you add Doxygen with a `##` (`###`, etc) section header, it'll migrate to the wiki and be _removed_ from the headers. Generally the correct thing to do is _never use section headers in the Doxygen_. +## wikiheaders will reorder standard sections. + +The standard sections are always kept in a consistent order by +wikiheaders, both in the headers and the wiki. If they're placed in +a non-standard order, wikiheaders will reorder them. + +For sections that aren't standard, wikiheaders will place them at +the end of the wiki page, in the order they were seen when it loaded +the page for processing. + ## It's okay to repeat yourself. Each individual piece of documentation becomes a separate page on the wiki, so From 1dbb813316dd2dcc89e0252557047c165497a061 Mon Sep 17 00:00:00 2001 From: Ivan Epifanov Date: Tue, 6 May 2025 21:10:34 +0300 Subject: [PATCH 18/83] VITA: fix audio playback --- src/audio/vita/SDL_vitaaudio.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/audio/vita/SDL_vitaaudio.c b/src/audio/vita/SDL_vitaaudio.c index e194f212dd..86e8a691c0 100644 --- a/src/audio/vita/SDL_vitaaudio.c +++ b/src/audio/vita/SDL_vitaaudio.c @@ -130,7 +130,8 @@ static bool VITAAUD_OpenDevice(SDL_AudioDevice *device) static bool VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { - return (sceAudioOutOutput(device->hidden->port, buffer) == 0); + // sceAudioOutOutput returns amount of samples queued or < 0 on error + return (sceAudioOutOutput(device->hidden->port, buffer) >= 0); } // This function waits until it is possible to write a full sound buffer From 8e1f4bafb449ee6cc223dd4c62a45deb791dc47e Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 6 May 2025 13:04:37 -0700 Subject: [PATCH 19/83] [emscripten] Remove referenc to Module['createContext'] The Module interface is the one used by the outside world. This code is inside the module itself so can use the internal name, avoiding the need to export this function on the Module at all. See https://github.com/emscripten-core/emscripten/pull/24269 --- src/video/emscripten/SDL_emscriptenframebuffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video/emscripten/SDL_emscriptenframebuffer.c b/src/video/emscripten/SDL_emscriptenframebuffer.c index 503fac6804..89fae738d7 100644 --- a/src/video/emscripten/SDL_emscriptenframebuffer.c +++ b/src/video/emscripten/SDL_emscriptenframebuffer.c @@ -78,7 +78,7 @@ bool Emscripten_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wind if (!Module['SDL3']) Module['SDL3'] = {}; var SDL3 = Module['SDL3']; if (SDL3.ctxCanvas !== canvas) { - SDL3.ctx = Module['createContext'](canvas, false, true); + SDL3.ctx = Browser.createContext(canvas, false, true); SDL3.ctxCanvas = canvas; } if (SDL3.w !== w || SDL3.h !== h || SDL3.imageCtx !== SDL3.ctx) { From f4813ca2cf9953f60ec406ab63e07bd68f56c4ee Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Tue, 6 May 2025 14:17:00 -0400 Subject: [PATCH 20/83] x11: Filter out duplicate key presses when an IME is active IME text events can result in sending duplicate key press events, which will result in undesired repeated key presses. Since the events are exact duplicates, compare the serials to filter out redundant key down events. --- src/video/x11/SDL_x11events.c | 8 ++++++-- src/video/x11/SDL_x11video.h | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index a4e5d9caf6..364e05a779 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -988,8 +988,11 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_ } if (pressed) { - X11_HandleModifierKeys(videodata, scancode, true, true); - SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true); + // Duplicate events may be sent when an IME is active; don't send multiple keydown events for the same serial. + if (videodata->last_key_down_serial != xevent->xkey.serial) { + X11_HandleModifierKeys(videodata, scancode, true, true); + SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true); + } // Synthesize a text event if the IME didn't consume a printable character if (*text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) { @@ -999,6 +1002,7 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_ } X11_UpdateUserTime(windowdata, xevent->xkey.time); + videodata->last_key_down_serial = xevent->xkey.serial; } else { if (X11_KeyRepeat(display, xevent)) { // We're about to get a repeated key down, ignore the key up diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h index a336a800f5..d5a9299b4e 100644 --- a/src/video/x11/SDL_x11video.h +++ b/src/video/x11/SDL_x11video.h @@ -140,6 +140,8 @@ struct SDL_VideoData int xinput_master_pointer_device; bool xinput_hierarchy_changed; + unsigned long last_key_down_serial; + int xrandr_event_base; struct { From 195ad85ba4c5507d4a03d72ab2ff3db3e566ce43 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 6 May 2025 16:08:03 -0700 Subject: [PATCH 21/83] Fixed typo --- src/video/openvr/SDL_openvrvideo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video/openvr/SDL_openvrvideo.c b/src/video/openvr/SDL_openvrvideo.c index 54e44ba43b..03d634578e 100644 --- a/src/video/openvr/SDL_openvrvideo.c +++ b/src/video/openvr/SDL_openvrvideo.c @@ -712,7 +712,7 @@ static bool OPENVR_ReleaseFrame(SDL_VideoDevice *_this) if (videodata->overlaytexture != 0 && videodata->targh == videodata->last_targh && videodata->targw == videodata->last_targw) { - // Only submit frames to OpenVR if the textu re exists. + // Only submit frames to OpenVR if the texture exists. struct Texture_t tex; // Setup a Texture_t object to send in the texture. From ca47dc59a9106af4a3c1a4011695f9baeb987f8e Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 6 May 2025 16:14:22 -0700 Subject: [PATCH 22/83] Fixed building with the OpenVR video driver --- src/video/openvr/SDL_openvrvideo.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/video/openvr/SDL_openvrvideo.c b/src/video/openvr/SDL_openvrvideo.c index 03d634578e..a24cd02fbe 100644 --- a/src/video/openvr/SDL_openvrvideo.c +++ b/src/video/openvr/SDL_openvrvideo.c @@ -542,12 +542,12 @@ static bool OPENVR_SetupJoystickBasedOnLoadedActionManifest(SDL_VideoData * vide virtual_id = SDL_AttachVirtualJoystick(&desc); if (!virtual_id) { + return SDL_SetError("OPENVR: Couldn't attach virtual joystick device: %s", SDL_GetError()); + } + + videodata->virtual_joystick = SDL_OpenJoystick(virtual_id); + if (!videodata->virtual_joystick) { return SDL_SetError("OPENVR: Couldn't open virtual joystick device: %s", SDL_GetError()); - } else { - videodata->virtual_joystick = SDL_OpenJoystick(virtual_index); - if (!videodata->virtual_joystick) { - return SDL_SetError("OPENVR: Couldn't open virtual joystick device: %s", SDL_GetError()); - } } #ifdef DEBUG_OPENVR From 2b3c481215c9ff57510a62d57aa1601f54af58af Mon Sep 17 00:00:00 2001 From: 8BitDo <78518087+8BitDo@users.noreply.github.com> Date: Wed, 7 May 2025 10:47:10 +0800 Subject: [PATCH 23/83] add 8BitDo Controller (#12964) add SN30 Pro, SF30 Pro, Pro 2. Supported versions: Pro 2 v3.06 above SF30 Pro/SN30 Pro v2.05 above --- src/joystick/SDL_joystick.c | 15 ++++++++++++- src/joystick/hidapi/SDL_hidapi_8bitdo.c | 28 ++++++++++++++++++++++++- src/joystick/usb_ids.h | 5 +++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index df6988909a..c88b15cbe3 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -3177,7 +3177,20 @@ bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id) bool SDL_IsJoystick8BitDoController(Uint16 vendor_id, Uint16 product_id) { - return vendor_id == USB_VENDOR_8BITDO && (product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS); + if (vendor_id == USB_VENDOR_8BITDO) { + switch (product_id) { + case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: + case USB_PRODUCT_8BITDO_SN30_PRO: + case USB_PRODUCT_8BITDO_SN30_PRO_BT: + case USB_PRODUCT_8BITDO_SF30_PRO: + case USB_PRODUCT_8BITDO_PRO_2: + case USB_PRODUCT_8BITDO_PRO_2_BT: + return true; + default: + break; + } + } + return false; } bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id) diff --git a/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/src/joystick/hidapi/SDL_hidapi_8bitdo.c index ca60118006..1f12f85957 100644 --- a/src/joystick/hidapi/SDL_hidapi_8bitdo.c +++ b/src/joystick/hidapi/SDL_hidapi_8bitdo.c @@ -42,6 +42,11 @@ enum SDL_GAMEPAD_NUM_8BITDO_BUTTONS, }; +#define SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID 0x06 +#define SDL_8BITDO_REPORTID_SDL_REPORTID 0x04 +#define SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID 0x03 +#define SDL_8BITDO_BT_REPORTID_SDL_REPORTID 0x01 + #define ABITDO_ACCEL_SCALE 4096.f #define SENSOR_INTERVAL_NS 8000000ULL @@ -112,6 +117,13 @@ static bool HIDAPI_Driver8BitDo_IsEnabled(void) return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); } +static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length) +{ + SDL_memset(report, 0, length); + report[0] = report_id; + return SDL_hid_get_feature_report(dev, report, length); +} + static bool HIDAPI_Driver8BitDo_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) { return SDL_IsJoystick8BitDoController(vendor_id, product_id); @@ -135,6 +147,19 @@ static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) ctx->rumble_supported = true; ctx->powerstate_supported = true; } + } else if (device->product_id == USB_PRODUCT_8BITDO_SN30_PRO || device->product_id == USB_PRODUCT_8BITDO_SN30_PRO_BT || + device->product_id == USB_PRODUCT_8BITDO_SF30_PRO || device->product_id == USB_PRODUCT_8BITDO_PRO_2 || + device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT) { + Uint8 data[USB_PACKET_LENGTH]; + int size = ReadFeatureReport(device->dev, SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID, data, sizeof(data)); + if (size > 0) { + ctx->sensors_supported = true; + ctx->rumble_supported = true; + ctx->powerstate_supported = true; + } else { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "HIDAPI_Driver8BitDo_InitDevice(): Couldn't read feature report 0x06"); + } } return HIDAPI_JoystickConnected(device, NULL); @@ -236,7 +261,8 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr { Sint16 axis; Uint64 timestamp = SDL_GetTicksNS(); - if (data[0] != 0x03 && data[0] != 0x01) { + if (data[0] != SDL_8BITDO_REPORTID_SDL_REPORTID && data[0] != SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID && + data[0] != SDL_8BITDO_BT_REPORTID_SDL_REPORTID) { // We don't know how to handle this report return; } diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index 6692cd412d..812d0a4805 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -60,6 +60,11 @@ #define USB_VENDOR_ZEROPLUS 0x0c12 #define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012 +#define USB_PRODUCT_8BITDO_SN30_PRO 0x6001 // B + START +#define USB_PRODUCT_8BITDO_SN30_PRO_BT 0x6101 // B + START +#define USB_PRODUCT_8BITDO_SF30_PRO 0x6000 // B + START +#define USB_PRODUCT_8BITDO_PRO_2 0x6003 // mode switch to D +#define USB_PRODUCT_8BITDO_PRO_2_BT 0x6006 // mode switch to D #define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419 #define USB_PRODUCT_ASTRO_C40_XBOX360 0x0024 #define USB_PRODUCT_BACKBONE_ONE_IOS 0x0103 From 6b048f59d74e67189965edd783574bb1a98d4ba4 Mon Sep 17 00:00:00 2001 From: expikr <77922942+expikr@users.noreply.github.com> Date: Wed, 7 May 2025 12:00:12 +0800 Subject: [PATCH 24/83] fix #12963 --- src/video/windows/SDL_windowsevents.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index e1f0da990e..ad55c6711f 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -1400,8 +1400,6 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara } } - return 0; - } break; case WM_LBUTTONUP: From 89a8cf2505a5f622654168564dcb44fe1327761c Mon Sep 17 00:00:00 2001 From: SDL Wiki Bot Date: Wed, 7 May 2025 05:54:39 +0000 Subject: [PATCH 25/83] Sync SDL3 wiki -> header [ci skip] --- docs/README-emscripten.md | 2 +- include/SDL3/SDL_init.h | 2 +- include/SDL3/SDL_main.h | 8 ++++---- include/SDL3/SDL_system.h | 4 ++-- include/SDL3/SDL_video.h | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/README-emscripten.md b/docs/README-emscripten.md index c1d16b8fe9..54e7d2de50 100644 --- a/docs/README-emscripten.md +++ b/docs/README-emscripten.md @@ -103,7 +103,7 @@ getting started. Another option is to use SDL' main callbacks, which handle this for you without platform-specific code in your app. Please refer to -[the wiki](https://wiki.libsdl.org/SDL3/README/main-functions#main-callbacks-in-sdl3) +[the wiki](https://wiki.libsdl.org/SDL3/README-main-functions#main-callbacks-in-sdl3) or `docs/README-main-functions.md` in the SDL source code. diff --git a/include/SDL3/SDL_init.h b/include/SDL3/SDL_init.h index adf0de8a21..af6f0a3e69 100644 --- a/include/SDL3/SDL_init.h +++ b/include/SDL3/SDL_init.h @@ -101,7 +101,7 @@ typedef Uint32 SDL_InitFlags; * to run. * * See - * [Main callbacks in SDL3](https://wiki.libsdl.org/SDL3/README/main-functions#main-callbacks-in-sdl3) + * [Main callbacks in SDL3](https://wiki.libsdl.org/SDL3/README-main-functions#main-callbacks-in-sdl3) * for complete details. * * \since This enum is available since SDL 3.2.0. diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h index 32775b4451..698611c3de 100644 --- a/include/SDL3/SDL_main.h +++ b/include/SDL3/SDL_main.h @@ -47,7 +47,7 @@ * * For more information, see: * - * https://wiki.libsdl.org/SDL3/README/main-functions + * https://wiki.libsdl.org/SDL3/README-main-functions */ #ifndef SDL_main_h_ @@ -68,7 +68,7 @@ * proper entry point for the platform, and all the other magic details * needed, like manually calling SDL_SetMainReady. * - * Please see [README/main-functions](README/main-functions), (or + * Please see [README-main-functions](README-main-functions), (or * docs/README-main-functions.md in the source tree) for a more detailed * explanation. * @@ -85,7 +85,7 @@ * SDL_AppQuit. The app should not provide a `main` function in this case, and * doing so will likely cause the build to fail. * - * Please see [README/main-functions](README/main-functions), (or + * Please see [README-main-functions](README-main-functions), (or * docs/README-main-functions.md in the source tree) for a more detailed * explanation. * @@ -512,7 +512,7 @@ typedef int (SDLCALL *SDL_main_func)(int argc, char *argv[]); * SDL_MAIN_USE_CALLBACKS. * * Program startup is a surprisingly complex topic. Please see - * [README/main-functions](README/main-functions), (or + * [README-main-functions](README-main-functions), (or * docs/README-main-functions.md in the source tree) for a more detailed * explanation. * diff --git a/include/SDL3/SDL_system.h b/include/SDL3/SDL_system.h index 294089ff4a..625db182e6 100644 --- a/include/SDL3/SDL_system.h +++ b/include/SDL3/SDL_system.h @@ -247,14 +247,14 @@ typedef void (SDLCALL *SDL_iOSAnimationCallback)(void *userdata); * * For more information see: * - * https://wiki.libsdl.org/SDL3/README/ios + * https://wiki.libsdl.org/SDL3/README-ios * * Note that if you use the "main callbacks" instead of a standard C `main` * function, you don't have to use this API, as SDL will manage this for you. * * Details on main callbacks are here: * - * https://wiki.libsdl.org/SDL3/README/main-functions + * https://wiki.libsdl.org/SDL3/README-main-functions * * \param window the window for which the animation callback should be set. * \param interval the number of frames after which **callback** will be diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index ca5460635b..e56a6ae10e 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -1303,7 +1303,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren * - `SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN` - true if * the application wants to use the Wayland surface for a custom role and * does not want it attached to an XDG toplevel window. See - * [README/wayland](README/wayland) for more information on using custom + * [README-wayland](README-wayland) for more information on using custom * surfaces. * - `SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN` - true if the * application wants an associated `wl_egl_window` object to be created and @@ -1311,7 +1311,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren * property or `SDL_WINDOW_OPENGL` flag set. * - `SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER` - the wl_surface * associated with the window, if you want to wrap an existing window. See - * [README/wayland](README/wayland) for more information. + * [README-wayland](README-wayland) for more information. * * These are additional supported properties on Windows: * From 5bee85408c66638a26f91150f919d159b8e37cba Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 7 May 2025 11:53:55 -0700 Subject: [PATCH 26/83] Cleanup 8BitDo HIDAPI support for SF30 Pro and SN30 Pro This sets the correct number of buttons for older controllers, and adds parsing for older firmware USB reports --- src/joystick/SDL_gamepad.c | 26 +++-- src/joystick/SDL_joystick.c | 18 ---- src/joystick/SDL_joystick_c.h | 3 - src/joystick/hidapi/SDL_hidapi_8bitdo.c | 137 +++++++++++++++++++++--- src/joystick/usb_ids.h | 3 +- 5 files changed, 146 insertions(+), 41 deletions(-) diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index 5f08f94752..32d76df011 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -779,6 +779,20 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) } break; } + } else if (vendor == USB_VENDOR_8BITDO && + (product == USB_PRODUCT_8BITDO_SN30_PRO || + product == USB_PRODUCT_8BITDO_SN30_PRO_BT || + product == USB_PRODUCT_8BITDO_PRO_2 || + product == USB_PRODUCT_8BITDO_PRO_2_BT)) { + SDL_strlcat(mapping_string, "a:b1,b:b0,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + if (product == USB_PRODUCT_8BITDO_PRO_2 || product == USB_PRODUCT_8BITDO_PRO_2_BT) { + SDL_strlcat(mapping_string, "paddle1:b14,paddle2:b13,", sizeof(mapping_string)); + } + } else if (vendor == USB_VENDOR_8BITDO && + (product == USB_PRODUCT_8BITDO_SF30_PRO || + product == USB_PRODUCT_8BITDO_SF30_PRO_BT)) { + // This controller has no guide button + SDL_strlcat(mapping_string, "a:b1,b:b0,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); } else { // All other gamepads have the standard set of 19 buttons and 6 axes if (SDL_IsJoystickGameCube(vendor, product)) { @@ -802,20 +816,20 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); } else if (SDL_IsJoystickGoogleStadiaController(vendor, product)) { // The Google Stadia controller has a share button and a Google Assistant button - SDL_strlcat(mapping_string, "misc1:b11,misc2:b12", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "misc1:b11,misc2:b12,", sizeof(mapping_string)); } else if (SDL_IsJoystickNVIDIASHIELDController(vendor, product)) { // The NVIDIA SHIELD controller has a share button between back and start buttons SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); if (product == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) { // The original SHIELD controller has a touchpad and plus/minus buttons as well - SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14,", sizeof(mapping_string)); } } else if (SDL_IsJoystickHoriSteamController(vendor, product)) { /* The Wireless HORIPad for Steam has QAM, Steam, Capsense L/R Sticks, 2 rear buttons, and 2 misc buttons */ - SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17", sizeof(mapping_string)); - } else if (SDL_IsJoystick8BitDoController(vendor, product)) { - SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17,", sizeof(mapping_string)); + } else if (vendor == USB_VENDOR_8BITDO && product == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { + SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13,", sizeof(mapping_string)); } else { switch (SDL_GetGamepadTypeFromGUID(guid, NULL)) { case SDL_GAMEPAD_TYPE_PS4: @@ -1295,7 +1309,7 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG static bool SDL_PrivateParseGamepadConfigString(SDL_Gamepad *gamepad, const char *pchString) { char szGameButton[20]; - char szJoystickButton[20]; + char szJoystickButton[128]; bool bGameButton = true; int i = 0; const char *pchPos = pchString; diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index c88b15cbe3..3caf227f01 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -3175,24 +3175,6 @@ bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id) return vendor_id == USB_VENDOR_HORI && (product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER || product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT); } -bool SDL_IsJoystick8BitDoController(Uint16 vendor_id, Uint16 product_id) -{ - if (vendor_id == USB_VENDOR_8BITDO) { - switch (product_id) { - case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: - case USB_PRODUCT_8BITDO_SN30_PRO: - case USB_PRODUCT_8BITDO_SN30_PRO_BT: - case USB_PRODUCT_8BITDO_SF30_PRO: - case USB_PRODUCT_8BITDO_PRO_2: - case USB_PRODUCT_8BITDO_PRO_2_BT: - return true; - default: - break; - } - } - return false; -} - bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id) { EControllerType eType = GuessControllerType(vendor_id, product_id); diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h index 61c7b32bbe..6b82365c58 100644 --- a/src/joystick/SDL_joystick_c.h +++ b/src/joystick/SDL_joystick_c.h @@ -135,9 +135,6 @@ extern bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id); // Function to return whether a joystick is a HORI Steam controller extern bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id); -// Function to return whether a joystick is a 8BitDo controller -extern bool SDL_IsJoystick8BitDoController(Uint16 vendor_id, Uint16 product_id); - // Function to return whether a joystick is a Steam Deck extern bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id); diff --git a/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/src/joystick/hidapi/SDL_hidapi_8bitdo.c index 1f12f85957..82efaf7019 100644 --- a/src/joystick/hidapi/SDL_hidapi_8bitdo.c +++ b/src/joystick/hidapi/SDL_hidapi_8bitdo.c @@ -126,7 +126,21 @@ static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report static bool HIDAPI_Driver8BitDo_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) { - return SDL_IsJoystick8BitDoController(vendor_id, product_id); + if (vendor_id == USB_VENDOR_8BITDO) { + switch (product_id) { + case USB_PRODUCT_8BITDO_SF30_PRO: + case USB_PRODUCT_8BITDO_SF30_PRO_BT: + case USB_PRODUCT_8BITDO_SN30_PRO: + case USB_PRODUCT_8BITDO_SN30_PRO_BT: + case USB_PRODUCT_8BITDO_PRO_2: + case USB_PRODUCT_8BITDO_PRO_2_BT: + case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: + return true; + default: + break; + } + } + return false; } static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) @@ -147,21 +161,24 @@ static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) ctx->rumble_supported = true; ctx->powerstate_supported = true; } - } else if (device->product_id == USB_PRODUCT_8BITDO_SN30_PRO || device->product_id == USB_PRODUCT_8BITDO_SN30_PRO_BT || - device->product_id == USB_PRODUCT_8BITDO_SF30_PRO || device->product_id == USB_PRODUCT_8BITDO_PRO_2 || - device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT) { + } else { Uint8 data[USB_PACKET_LENGTH]; int size = ReadFeatureReport(device->dev, SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID, data, sizeof(data)); if (size > 0) { ctx->sensors_supported = true; ctx->rumble_supported = true; ctx->powerstate_supported = true; - } else { - SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, - "HIDAPI_Driver8BitDo_InitDevice(): Couldn't read feature report 0x06"); } } + if (device->product_id == USB_PRODUCT_8BITDO_SF30_PRO || device->product_id == USB_PRODUCT_8BITDO_SF30_PRO_BT) { + HIDAPI_SetDeviceName(device, "8BitDo SF30 Pro"); + } else if (device->product_id == USB_PRODUCT_8BITDO_SN30_PRO || device->product_id == USB_PRODUCT_8BITDO_SN30_PRO_BT) { + HIDAPI_SetDeviceName(device, "8BitDo SN30 Pro"); + } else if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT) { + HIDAPI_SetDeviceName(device, "8BitDo Pro 2"); + } + return HIDAPI_JoystickConnected(device, NULL); } @@ -187,7 +204,14 @@ static bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys SDL_zeroa(ctx->last_state); // Initialize the joystick capabilities - joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS; + if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || + device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT || + device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { + // This controller has additional buttons + joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS; + } else { + joystick->nbuttons = 11; + } joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; joystick->nhats = 1; @@ -257,12 +281,95 @@ static bool HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *dev } return SDL_Unsupported(); } + +static void HIDAPI_Driver8BitDo_HandleOldStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[2] != data[2]) { + Uint8 hat; + + switch (data[2]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + if (ctx->last_state[0] != data[0]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x80) != 0)); + } + + if (ctx->last_state[1] != data[1]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x40) != 0)); + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[1] & 0x01) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[1] & 0x02) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(3); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(4); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(5); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(6); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size) { Sint16 axis; Uint64 timestamp = SDL_GetTicksNS(); - if (data[0] != SDL_8BITDO_REPORTID_SDL_REPORTID && data[0] != SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID && - data[0] != SDL_8BITDO_BT_REPORTID_SDL_REPORTID) { + + switch (data[0]) { + case SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID: // Firmware without enhanced mode + case SDL_8BITDO_REPORTID_SDL_REPORTID: // Enhanced mode USB report + case SDL_8BITDO_BT_REPORTID_SDL_REPORTID: // Enhanced mode Bluetooth report + break; + default: // We don't know how to handle this report return; } @@ -323,7 +430,7 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[9] & 0x40) != 0)); } - if (ctx->last_state[10] != data[10]) { + if (size > 10 && ctx->last_state[10] != data[10]) { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_L4, ((data[10] & 0x01) != 0)); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_R4, ((data[10] & 0x02) != 0)); } @@ -381,7 +488,6 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr SDL_SendJoystickPowerInfo(joystick, state, percent); } - if (ctx->sensors_enabled) { Uint64 sensor_timestamp; float values[3]; @@ -440,7 +546,12 @@ static bool HIDAPI_Driver8BitDo_UpdateDevice(SDL_HIDAPI_Device *device) continue; } - HIDAPI_Driver8BitDo_HandleStatePacket(joystick, ctx, data, size); + if (size == 9) { + // Old firmware USB report for the SF30 Pro and SN30 Pro controllers + HIDAPI_Driver8BitDo_HandleOldStatePacket(joystick, ctx, data, size); + } else { + HIDAPI_Driver8BitDo_HandleStatePacket(joystick, ctx, data, size); + } } if (size < 0) { diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index 812d0a4805..323283f6ea 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -60,9 +60,10 @@ #define USB_VENDOR_ZEROPLUS 0x0c12 #define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012 +#define USB_PRODUCT_8BITDO_SF30_PRO 0x6000 // B + START +#define USB_PRODUCT_8BITDO_SF30_PRO_BT 0x6100 // B + START #define USB_PRODUCT_8BITDO_SN30_PRO 0x6001 // B + START #define USB_PRODUCT_8BITDO_SN30_PRO_BT 0x6101 // B + START -#define USB_PRODUCT_8BITDO_SF30_PRO 0x6000 // B + START #define USB_PRODUCT_8BITDO_PRO_2 0x6003 // mode switch to D #define USB_PRODUCT_8BITDO_PRO_2_BT 0x6006 // mode switch to D #define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419 From e7a765d6485045f24e453e8bc6358df843036490 Mon Sep 17 00:00:00 2001 From: Semphris Date: Wed, 7 May 2025 15:20:22 -0400 Subject: [PATCH 27/83] Replace SDL_free with delete for new-allocated objects --- src/dialog/haiku/SDL_haikudialog.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dialog/haiku/SDL_haikudialog.cc b/src/dialog/haiku/SDL_haikudialog.cc index d60e343439..fbef8fa214 100644 --- a/src/dialog/haiku/SDL_haikudialog.cc +++ b/src/dialog/haiku/SDL_haikudialog.cc @@ -251,9 +251,9 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil SDLBRefFilter *filter = new(std::nothrow) SDLBRefFilter(filters, nfilters); if (looper == NULL || messenger == NULL || filter == NULL) { - SDL_free(looper); - SDL_free(messenger); - SDL_free(filter); + delete looper; + delete messenger; + delete filter; SDL_OutOfMemory(); callback(userdata, NULL, -1); return; From d9e58baa6d089b706fee20888c156c78d731d70a Mon Sep 17 00:00:00 2001 From: Ozkan Sezer Date: Thu, 8 May 2025 14:45:24 +0300 Subject: [PATCH 28/83] fixed the field of SDL_MouseMotionTransformCallback --- include/SDL3/SDL_mouse.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/SDL3/SDL_mouse.h b/include/SDL3/SDL_mouse.h index 4e522251a6..911636b9b4 100644 --- a/include/SDL3/SDL_mouse.h +++ b/include/SDL3/SDL_mouse.h @@ -187,7 +187,7 @@ typedef Uint32 SDL_MouseButtonFlags; * with proper synchronization practices when adding other side * effects beyond mutation of the x and y values. * - * \since This datatype is available since SDL 3.2.6. + * \since This datatype is available since SDL 3.4.0. * * \sa SDL_SetRelativeMouseTransform */ From 37b86a6d2fee03136af4eddf14255f10353cfb6b Mon Sep 17 00:00:00 2001 From: nmlgc Date: Sat, 12 Apr 2025 20:04:27 +0200 Subject: [PATCH 29/83] windows: fix Unicode function and type inconsistencies The surrounding code in all of these instances expects the Unicode variants. Previously, this code mixed Unicode and ANSI/ASCII calls if `UNICODE` was undefined, which caused type and logic errors. Explicitly spelling out the W removes any reliance on that macro. --- .../mediafoundation/SDL_camera_mediafoundation.c | 4 ++-- src/core/windows/SDL_windows.c | 16 ++++++++-------- src/filesystem/windows/SDL_sysfilesystem.c | 2 +- src/joystick/windows/SDL_dinputjoystick.c | 2 +- src/video/windows/SDL_windowsclipboard.c | 4 ++-- src/video/windows/SDL_windowsmodes.c | 10 +++++----- src/video/windows/SDL_windowsmodes.h | 2 +- src/video/windows/SDL_windowswindow.c | 8 ++++---- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/camera/mediafoundation/SDL_camera_mediafoundation.c b/src/camera/mediafoundation/SDL_camera_mediafoundation.c index d9d627d0ab..aec728ea8f 100644 --- a/src/camera/mediafoundation/SDL_camera_mediafoundation.c +++ b/src/camera/mediafoundation/SDL_camera_mediafoundation.c @@ -741,7 +741,7 @@ static bool MEDIAFOUNDATION_OpenDevice(SDL_Camera *device, const SDL_CameraSpec SDL_Log("CAMERA: opening device with symlink of '%s'", utf8symlink); #endif - wstrsymlink = WIN_UTF8ToString(utf8symlink); + wstrsymlink = WIN_UTF8ToStringW(utf8symlink); if (!wstrsymlink) { goto failed; } @@ -901,7 +901,7 @@ static char *QueryActivationObjectString(IMFActivate *activation, const GUID *pg return NULL; } - char *utf8str = WIN_StringToUTF8(wstr); + char *utf8str = WIN_StringToUTF8W(wstr); CoTaskMemFree(wstr); return utf8str; } diff --git a/src/core/windows/SDL_windows.c b/src/core/windows/SDL_windows.c index 3259e787d4..d96d8e0bba 100644 --- a/src/core/windows/SDL_windows.c +++ b/src/core/windows/SDL_windows.c @@ -261,7 +261,7 @@ char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid) char *result = NULL; if (WIN_IsEqualGUID(guid, &nullguid)) { - return WIN_StringToUTF8(name); // No GUID, go with what we've got. + return WIN_StringToUTF8W(name); // No GUID, go with what we've got. } ptr = (const unsigned char *)guid; @@ -270,37 +270,37 @@ char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid) ptr[3], ptr[2], ptr[1], ptr[0], ptr[5], ptr[4], ptr[7], ptr[6], ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15]); - strw = WIN_UTF8ToString(keystr); + strw = WIN_UTF8ToStringW(keystr); rc = (RegOpenKeyExW(HKEY_LOCAL_MACHINE, strw, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS); SDL_free(strw); if (!rc) { - return WIN_StringToUTF8(name); // oh well. + return WIN_StringToUTF8W(name); // oh well. } rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, NULL, &len) == ERROR_SUCCESS); if (!rc) { RegCloseKey(hkey); - return WIN_StringToUTF8(name); // oh well. + return WIN_StringToUTF8W(name); // oh well. } strw = (WCHAR *)SDL_malloc(len + sizeof(WCHAR)); if (!strw) { RegCloseKey(hkey); - return WIN_StringToUTF8(name); // oh well. + return WIN_StringToUTF8W(name); // oh well. } rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, (LPBYTE)strw, &len) == ERROR_SUCCESS); RegCloseKey(hkey); if (!rc) { SDL_free(strw); - return WIN_StringToUTF8(name); // oh well. + return WIN_StringToUTF8W(name); // oh well. } strw[len / 2] = 0; // make sure it's null-terminated. - result = WIN_StringToUTF8(strw); + result = WIN_StringToUTF8W(strw); SDL_free(strw); - return result ? result : WIN_StringToUTF8(name); + return result ? result : WIN_StringToUTF8W(name); #endif } diff --git a/src/filesystem/windows/SDL_sysfilesystem.c b/src/filesystem/windows/SDL_sysfilesystem.c index 39ba414895..a4c033f068 100644 --- a/src/filesystem/windows/SDL_sysfilesystem.c +++ b/src/filesystem/windows/SDL_sysfilesystem.c @@ -181,7 +181,7 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) char *SDL_SYS_GetUserFolder(SDL_Folder folder) { typedef HRESULT (WINAPI *pfnSHGetKnownFolderPath)(REFGUID /* REFKNOWNFOLDERID */, DWORD, HANDLE, PWSTR*); - HMODULE lib = LoadLibrary(L"Shell32.dll"); + HMODULE lib = LoadLibraryW(L"Shell32.dll"); pfnSHGetKnownFolderPath pSHGetKnownFolderPath = NULL; char *result = NULL; diff --git a/src/joystick/windows/SDL_dinputjoystick.c b/src/joystick/windows/SDL_dinputjoystick.c index b00218d969..a96388239f 100644 --- a/src/joystick/windows/SDL_dinputjoystick.c +++ b/src/joystick/windows/SDL_dinputjoystick.c @@ -293,7 +293,7 @@ static bool QueryDeviceName(LPDIRECTINPUTDEVICE8 device, Uint16 vendor_id, Uint1 } *manufacturer_string = NULL; - *product_string = WIN_StringToUTF8(dipstr.wsz); + *product_string = WIN_StringToUTF8W(dipstr.wsz); return true; } diff --git a/src/video/windows/SDL_windowsclipboard.c b/src/video/windows/SDL_windowsclipboard.c index 39f86ef32f..4d1e97666f 100644 --- a/src/video/windows/SDL_windowsclipboard.c +++ b/src/video/windows/SDL_windowsclipboard.c @@ -193,7 +193,7 @@ static bool WIN_SetClipboardText(SDL_VideoDevice *_this, const char *mime_type) clipboard_data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, &clipboard_data_size); if (clipboard_data && clipboard_data_size > 0) { SIZE_T i, size; - LPTSTR tstr = (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)clipboard_data, clipboard_data_size); + LPWSTR tstr = (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)clipboard_data, clipboard_data_size); if (!tstr) { return SDL_SetError("Couldn't convert text from UTF-8"); } @@ -210,7 +210,7 @@ static bool WIN_SetClipboardText(SDL_VideoDevice *_this, const char *mime_type) // Save the data to the clipboard hMem = GlobalAlloc(GMEM_MOVEABLE, size); if (hMem) { - LPTSTR dst = (LPTSTR)GlobalLock(hMem); + LPWSTR dst = (LPWSTR)GlobalLock(hMem); if (dst) { // Copy the text over, adding carriage returns as necessary for (i = 0; tstr[i]; ++i) { diff --git a/src/video/windows/SDL_windowsmodes.c b/src/video/windows/SDL_windowsmodes.c index 77ebab29a5..34c5bd7316 100644 --- a/src/video/windows/SDL_windowsmodes.c +++ b/src/video/windows/SDL_windowsmodes.c @@ -46,7 +46,7 @@ static void WIN_UpdateDisplayMode(SDL_VideoDevice *_this, LPCWSTR deviceName, DW data->DeviceMode.dmFields = (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS); // NOLINTNEXTLINE(bugprone-assignment-in-if-condition): No simple way to extract the assignment - if (index == ENUM_CURRENT_SETTINGS && (hdc = CreateDC(deviceName, NULL, NULL, NULL)) != NULL) { + if (index == ENUM_CURRENT_SETTINGS && (hdc = CreateDCW(deviceName, NULL, NULL, NULL)) != NULL) { char bmi_data[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)]; LPBITMAPINFO bmi; HBITMAP hbm; @@ -158,7 +158,7 @@ static void WIN_ReleaseDXGIOutput(void *dxgi_output) #endif } -static SDL_DisplayOrientation WIN_GetNaturalOrientation(DEVMODE *mode) +static SDL_DisplayOrientation WIN_GetNaturalOrientation(DEVMODEW *mode) { int width = mode->dmPelsWidth; int height = mode->dmPelsHeight; @@ -177,7 +177,7 @@ static SDL_DisplayOrientation WIN_GetNaturalOrientation(DEVMODE *mode) } } -static SDL_DisplayOrientation WIN_GetDisplayOrientation(DEVMODE *mode) +static SDL_DisplayOrientation WIN_GetDisplayOrientation(DEVMODEW *mode) { if (WIN_GetNaturalOrientation(mode) == SDL_ORIENTATION_LANDSCAPE) { switch (mode->dmDisplayOrientation) { @@ -208,7 +208,7 @@ static SDL_DisplayOrientation WIN_GetDisplayOrientation(DEVMODE *mode) } } -static void WIN_GetRefreshRate(void *dxgi_output, DEVMODE *mode, int *numerator, int *denominator) +static void WIN_GetRefreshRate(void *dxgi_output, DEVMODEW *mode, int *numerator, int *denominator) { // We're not currently using DXGI to query display modes, so fake NTSC timings switch (mode->dmDisplayFrequency) { @@ -274,7 +274,7 @@ static float WIN_GetContentScale(SDL_VideoDevice *_this, HMONITOR hMonitor) static bool WIN_GetDisplayMode(SDL_VideoDevice *_this, void *dxgi_output, HMONITOR hMonitor, LPCWSTR deviceName, DWORD index, SDL_DisplayMode *mode, SDL_DisplayOrientation *natural_orientation, SDL_DisplayOrientation *current_orientation) { SDL_DisplayModeData *data; - DEVMODE devmode; + DEVMODEW devmode; devmode.dmSize = sizeof(devmode); devmode.dmDriverExtra = 0; diff --git a/src/video/windows/SDL_windowsmodes.h b/src/video/windows/SDL_windowsmodes.h index 3d294c31f0..e49817ca95 100644 --- a/src/video/windows/SDL_windowsmodes.h +++ b/src/video/windows/SDL_windowsmodes.h @@ -41,7 +41,7 @@ struct SDL_DisplayData struct SDL_DisplayModeData { - DEVMODE DeviceMode; + DEVMODEW DeviceMode; }; extern bool WIN_InitModes(SDL_VideoDevice *_this); diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index 079f3ba345..f61dc0e8f8 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -1450,7 +1450,7 @@ void *WIN_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t char *filename_utf8; void *iccProfileData = NULL; - filename_utf8 = WIN_StringToUTF8(data->ICMFileName); + filename_utf8 = WIN_StringToUTF8W(data->ICMFileName); if (filename_utf8) { iccProfileData = SDL_LoadFile(filename_utf8, size); if (!iccProfileData) { @@ -2061,7 +2061,7 @@ static STDMETHODIMP SDLDropTarget_Drop(SDLDropTarget *target, ". In Drop Text for GlobalLock, format %08x '%s', memory (%lu) %p", fetc.cfFormat, format_mime, (unsigned long)bsize, buffer); if (buffer) { - buffer = WIN_StringToUTF8((const wchar_t *)buffer); + buffer = WIN_StringToUTF8W((const wchar_t *)buffer); if (buffer) { const size_t lbuffer = SDL_strlen((const char *)buffer); SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, @@ -2208,8 +2208,8 @@ void WIN_AcceptDragAndDrop(SDL_Window *window, bool accept) drop_target->lpVtbl = vtDropTarget; drop_target->window = window; drop_target->hwnd = data->hwnd; - drop_target->format_file = RegisterClipboardFormat(L"text/uri-list"); - drop_target->format_text = RegisterClipboardFormat(L"text/plain;charset=utf-8"); + drop_target->format_file = RegisterClipboardFormatW(L"text/uri-list"); + drop_target->format_text = RegisterClipboardFormatW(L"text/plain;charset=utf-8"); data->drop_target = drop_target; SDLDropTarget_AddRef(drop_target); RegisterDragDrop(data->hwnd, (LPDROPTARGET)drop_target); From 09c8d4b55687d65f0f6052a60876308e95be3b44 Mon Sep 17 00:00:00 2001 From: SDL Wiki Bot Date: Thu, 8 May 2025 14:49:45 +0000 Subject: [PATCH 30/83] Sync SDL3 wiki -> header [ci skip] --- include/SDL3/SDL_gpu.h | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 70b7def89a..858b0ef318 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -206,8 +206,10 @@ * underlying graphics API. While it's possible that we have done something * inefficiently, it's very unlikely especially if you are relatively * inexperienced with GPU rendering. Please see the performance tips above and - * make sure you are following them. Additionally, tools like RenderDoc can be - * very helpful for diagnosing incorrect behavior and performance issues. + * make sure you are following them. Additionally, tools like + * [RenderDoc](https://renderdoc.org/) + * can be very helpful for diagnosing incorrect behavior and performance + * issues. * * ## System Requirements * @@ -333,6 +335,39 @@ * unreferenced data in a bound resource without cycling, but overwriting a * section of data that has already been referenced will produce unexpected * results. + * + * ## Debugging + * + * At some point of your GPU journey, you will probably encounter issues that + * are not traceable with regular debugger - for example, your code compiles + * but you get an empty screen, or your shader fails in runtime. + * + * For debugging such cases, there are tools that allow visually inspecting + * the whole GPU frame, every drawcall, every bound resource, memory buffers, + * etc. They are the following, per platform: + * + * * For Windows/Linux, use + * [RenderDoc](https://renderdoc.org/) + * * For MacOS (Metal), use Xcode built-in debugger (Open XCode, go to Debug > + * Debug Executable..., select your application, set "GPU Frame Capture" to + * "Metal" in scheme "Options" window, run your app, and click the small + * Metal icon on the bottom to capture a frame) + * + * Aside from that, you may want to enable additional debug layers to receive + * more detailed error messages, based on your GPU backend: + * + * * For D3D12, the debug layer is an optional feature that can be installed + * via "Windows Settings -> System -> Optional features" and adding the + * "Graphics Tools" optional feature. + * * For Vulkan, you will need to install Vulkan SDK on Windows, and on Linux, + * you usually have some sort of `vulkan-validation-layers` system package + * that should be installed. + * * For Metal, it should be enough just to run the application from XCode to + * receive detailed errors or warnings in the output. + * + * Don't hesitate to use tools as RenderDoc when encountering runtime issues + * or unexpected output on screen, quick GPU frame inspection can usually help + * you fix the majority of such problems. */ #ifndef SDL_gpu_h_ From d157600d3d611e4754d0cd4301cb04b9a37c16a1 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 8 May 2025 09:46:07 -0700 Subject: [PATCH 31/83] Added GCController mapping for the 8BitDo Pro 2 on macOS Fixes https://github.com/libsdl-org/SDL/issues/12987 --- src/joystick/SDL_gamepad_db.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/joystick/SDL_gamepad_db.h b/src/joystick/SDL_gamepad_db.h index 0bc9e708e1..bb0382d577 100644 --- a/src/joystick/SDL_gamepad_db.h +++ b/src/joystick/SDL_gamepad_db.h @@ -868,6 +868,7 @@ static const char *s_GamepadMappings[] = { "05000000ac05000001000000ff076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,", "05000000ac05000004000000a8986d04,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,lefttrigger:b12,rightshoulder:b13,righttrigger:b14,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac05000004000000fd216d04,8BitDo Pro 2,crc:ac95,platform:macOS,a:b3,b:b2,back:b6,dpdown:b9,dpleft:b10,dpright:b11,dpup:b12,guide:b4,leftshoulder:b13,leftstick:b14,lefttrigger:+a2,leftx:a0,lefty:a1~,paddle1:b1,paddle2:b0,rightshoulder:b16,rightstick:b17,righttrigger:+a5,rightx:a3,righty:a4~,start:b5,x:b8,y:b7,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000ac050000040000003b8a6d04,8BitDo SN30 Pro+,crc:3e00,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000008a35000003010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "050000008a35000004010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", From 2b57d58f7d462e804be0b48b9f48595b7cba7e65 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 8 May 2025 09:57:10 -0700 Subject: [PATCH 32/83] Added GCController mapping for the 8BitDo SN30 Pro on macOS --- src/joystick/SDL_gamepad_db.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/joystick/SDL_gamepad_db.h b/src/joystick/SDL_gamepad_db.h index bb0382d577..a654a28ec6 100644 --- a/src/joystick/SDL_gamepad_db.h +++ b/src/joystick/SDL_gamepad_db.h @@ -868,8 +868,9 @@ static const char *s_GamepadMappings[] = { "05000000ac05000001000000ff076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,", "05000000ac05000004000000a8986d04,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,lefttrigger:b12,rightshoulder:b13,righttrigger:b14,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "05000000ac05000004000000fd216d04,8BitDo Pro 2,crc:ac95,platform:macOS,a:b3,b:b2,back:b6,dpdown:b9,dpleft:b10,dpright:b11,dpup:b12,guide:b4,leftshoulder:b13,leftstick:b14,lefttrigger:+a2,leftx:a0,lefty:a1~,paddle1:b1,paddle2:b0,rightshoulder:b16,rightstick:b17,righttrigger:+a5,rightx:a3,righty:a4~,start:b5,x:b8,y:b7,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac05000004000000fd216d04,8BitDo Pro 2,crc:ac95,a:b3,b:b2,back:b6,dpdown:b9,dpleft:b10,dpright:b11,dpup:b12,guide:b4,leftshoulder:b13,leftstick:b14,lefttrigger:+a2,leftx:a0,lefty:a1~,paddle1:b1,paddle2:b0,rightshoulder:b16,rightstick:b17,righttrigger:+a5,rightx:a3,righty:a4~,start:b5,x:b8,y:b7,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000ac050000040000003b8a6d04,8BitDo SN30 Pro+,crc:3e00,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac05000004000000209f6d04,8Bitdo SN30 Pro,crc:40d6,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000008a35000003010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "050000008a35000004010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "050000007e050000062000000f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", From 8690a9ab543b66bf4fb90c8c3539f3eb849e8201 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 8 May 2025 10:07:48 -0700 Subject: [PATCH 33/83] Prefer the HIDAPI driver for 8BitDo controllers on macOS The HIDAPI driver supports accelerometer, gyro, and rumble. --- src/joystick/apple/SDL_mfijoystick.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/joystick/apple/SDL_mfijoystick.m b/src/joystick/apple/SDL_mfijoystick.m index 811a9f1ae7..001ef3145d 100644 --- a/src/joystick/apple/SDL_mfijoystick.m +++ b/src/joystick/apple/SDL_mfijoystick.m @@ -346,7 +346,9 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle (device->is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) || (device->is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, "")) || (device->is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) || - (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, ""))) { + (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, "")) || + (SDL_strcmp(name, "8Bitdo SN30 Pro") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO_BT, 0, ""))) || + (SDL_strcmp(name, "8BitDo Pro 2") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2_BT, 0, "")))) { // The HIDAPI driver is taking care of this device return false; } From 36c3a7a5e215693b6e128f246452e88342e429ec Mon Sep 17 00:00:00 2001 From: Ethan Lee Date: Tue, 6 May 2025 20:09:03 -0400 Subject: [PATCH 34/83] ci: Add slrsniper-arm64 task --- .github/workflows/create-test-plan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/create-test-plan.py b/.github/workflows/create-test-plan.py index 8048e2bc32..0c875c5c88 100755 --- a/.github/workflows/create-test-plan.py +++ b/.github/workflows/create-test-plan.py @@ -114,6 +114,7 @@ JOB_SPECS = { "ubuntu-22.04": JobSpec(name="Ubuntu 22.04", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04", ), "ubuntu-24.04-arm64": JobSpec(name="Ubuntu 24.04 (ARM64)", os=JobOs.Ubuntu24_04_arm, platform=SdlPlatform.Linux, artifact="SDL-ubuntu24.04-arm64", ), "steamrt-sniper": JobSpec(name="Steam Linux Runtime (Sniper)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Linux, artifact="SDL-slrsniper", container="registry.gitlab.steamos.cloud/steamrt/sniper/sdk:beta", ), + "steamrt-sniper-arm64": JobSpec(name="Steam Linux Runtime (arm64)", os=JobOs.Ubuntu24_04_arm, platform=SdlPlatform.Linux, artifact="SDL-slrsniper-arm64", container="registry.gitlab.steamos.cloud/steamrt/sniper/sdk/arm64:3.0.20250408.124536", ), "ubuntu-intel-icx": JobSpec(name="Ubuntu 22.04 (Intel oneAPI)", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04-oneapi", intel=IntelCompiler.Icx, ), "ubuntu-intel-icc": JobSpec(name="Ubuntu 22.04 (Intel Compiler)", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04-icc", intel=IntelCompiler.Icc, ), "macos-framework-x64": JobSpec(name="MacOS (Framework) (x64)", os=JobOs.Macos13, platform=SdlPlatform.MacOS, artifact="SDL-macos-framework", apple_framework=True, apple_archs={AppleArch.Aarch64, AppleArch.X86_64, }, xcode=True, ), From ad555977156ae925d6b7b0c0f48341b80d69a0fb Mon Sep 17 00:00:00 2001 From: Ethan Lee Date: Wed, 7 May 2025 11:35:06 -0400 Subject: [PATCH 35/83] ci: Update Steam Linux Runtime tasks. - Use "3.0" instead of "Sniper" - Use "steamrt3" instead of "slrsniper" - Use latest instead of beta for x86_64 --- .github/workflows/create-test-plan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-test-plan.py b/.github/workflows/create-test-plan.py index 0c875c5c88..04f4c10923 100755 --- a/.github/workflows/create-test-plan.py +++ b/.github/workflows/create-test-plan.py @@ -113,8 +113,8 @@ JOB_SPECS = { "msvc-gdk-x64": JobSpec(name="GDK (MSVC, x64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-VC-GDK", msvc_arch=MsvcArch.X64, msvc_project="VisualC-GDK/SDL.sln", gdk=True, no_cmake=True, ), "ubuntu-22.04": JobSpec(name="Ubuntu 22.04", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04", ), "ubuntu-24.04-arm64": JobSpec(name="Ubuntu 24.04 (ARM64)", os=JobOs.Ubuntu24_04_arm, platform=SdlPlatform.Linux, artifact="SDL-ubuntu24.04-arm64", ), - "steamrt-sniper": JobSpec(name="Steam Linux Runtime (Sniper)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Linux, artifact="SDL-slrsniper", container="registry.gitlab.steamos.cloud/steamrt/sniper/sdk:beta", ), - "steamrt-sniper-arm64": JobSpec(name="Steam Linux Runtime (arm64)", os=JobOs.Ubuntu24_04_arm, platform=SdlPlatform.Linux, artifact="SDL-slrsniper-arm64", container="registry.gitlab.steamos.cloud/steamrt/sniper/sdk/arm64:3.0.20250408.124536", ), + "steamrt3": JobSpec(name="Steam Linux Runtime 3.0 (x86_64)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Linux, artifact="SDL-steamrt3", container="registry.gitlab.steamos.cloud/steamrt/sniper/sdk:latest", ), + "steamrt3-arm64": JobSpec(name="Steam Linux Runtime 3.0 (arm64)", os=JobOs.Ubuntu24_04_arm, platform=SdlPlatform.Linux, artifact="SDL-steamrt3-arm64", container="registry.gitlab.steamos.cloud/steamrt/sniper/sdk/arm64:3.0.20250408.124536", ), "ubuntu-intel-icx": JobSpec(name="Ubuntu 22.04 (Intel oneAPI)", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04-oneapi", intel=IntelCompiler.Icx, ), "ubuntu-intel-icc": JobSpec(name="Ubuntu 22.04 (Intel Compiler)", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04-icc", intel=IntelCompiler.Icc, ), "macos-framework-x64": JobSpec(name="MacOS (Framework) (x64)", os=JobOs.Macos13, platform=SdlPlatform.MacOS, artifact="SDL-macos-framework", apple_framework=True, apple_archs={AppleArch.Aarch64, AppleArch.X86_64, }, xcode=True, ), From 2aa0957081366ef8d9df96c7921f81bb64c9a47c Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 8 May 2025 10:46:53 -0700 Subject: [PATCH 36/83] Fixed 8BitDo Ultimate 2 Wireless controller on macOS When connected for the first time over Bluetooth on macOS, the first few reads return 0, so retry a few times in that case. --- src/joystick/hidapi/SDL_hidapi_8bitdo.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/src/joystick/hidapi/SDL_hidapi_8bitdo.c index 82efaf7019..2dc5f92fda 100644 --- a/src/joystick/hidapi/SDL_hidapi_8bitdo.c +++ b/src/joystick/hidapi/SDL_hidapi_8bitdo.c @@ -154,12 +154,21 @@ static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) if (device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { // The Ultimate 2 Wireless v1.02 firmware has 12 byte reports, v1.03 firmware has 34 byte reports const int ULTIMATE2_WIRELESS_V103_REPORT_SIZE = 34; - Uint8 data[USB_PACKET_LENGTH]; - int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 80); - if (size >= ULTIMATE2_WIRELESS_V103_REPORT_SIZE) { - ctx->sensors_supported = true; - ctx->rumble_supported = true; - ctx->powerstate_supported = true; + const int MAX_ATTEMPTS = 3; + + for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { + Uint8 data[USB_PACKET_LENGTH]; + int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 80); + if (size == 0) { + // Try again + continue; + } + if (size >= ULTIMATE2_WIRELESS_V103_REPORT_SIZE) { + ctx->sensors_supported = true; + ctx->rumble_supported = true; + ctx->powerstate_supported = true; + } + break; } } else { Uint8 data[USB_PACKET_LENGTH]; From 29d21164951e5884c98bc8d817ffeceef76b00ff Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 8 May 2025 11:51:35 -0700 Subject: [PATCH 37/83] Define illegal_instruction() when it will be actually used --- src/cpuinfo/SDL_cpuinfo.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cpuinfo/SDL_cpuinfo.c b/src/cpuinfo/SDL_cpuinfo.c index a8feec846f..b836532800 100644 --- a/src/cpuinfo/SDL_cpuinfo.c +++ b/src/cpuinfo/SDL_cpuinfo.c @@ -115,7 +115,11 @@ #define CPU_CFG2_LSX (1 << 6) #define CPU_CFG2_LASX (1 << 7) -#if defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_OPENBSD) && !defined(SDL_PLATFORM_FREEBSD) && (defined(SDL_PLATFORM_LINUX) && !defined(HAVE_GETAUXVAL)) +#if !defined(SDL_CPUINFO_DISABLED) && \ + !((defined(SDL_PLATFORM_MACOS) && (defined(__ppc__) || defined(__ppc64__))) || (defined(SDL_PLATFORM_OPENBSD) && defined(__powerpc__))) && \ + !(defined(SDL_PLATFORM_FREEBSD) && defined(__powerpc__)) && \ + !(defined(SDL_PLATFORM_LINUX) && defined(__powerpc__) && defined(HAVE_GETAUXVAL)) && \ + defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) /* This is the brute force way of detecting instruction sets... the idea is borrowed from the libmpeg2 library - thanks! */ From 87fe9ef79b60ae86a2ca9d147b9c486c3078bf56 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 8 May 2025 15:38:32 -0700 Subject: [PATCH 38/83] Fixed crash if WGI isn't correlated in RAWINPUT_JoystickRumble() --- src/joystick/windows/SDL_rawinputjoystick.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/joystick/windows/SDL_rawinputjoystick.c b/src/joystick/windows/SDL_rawinputjoystick.c index 3e2b270fa9..f37a1d81ce 100644 --- a/src/joystick/windows/SDL_rawinputjoystick.c +++ b/src/joystick/windows/SDL_rawinputjoystick.c @@ -158,6 +158,7 @@ struct joystick_hwdata Uint8 wgi_correlation_count; Uint8 wgi_uncorrelate_count; WindowsGamingInputGamepadState *wgi_slot; + struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; #endif bool triggers_rumbling; @@ -449,7 +450,6 @@ typedef struct WindowsGamingInputGamepadState bool used; // Is currently mapped to an SDL device bool connected; // Just used during update to track disconnected Uint8 correlation_id; - struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; } WindowsGamingInputGamepadState; static struct @@ -1482,12 +1482,11 @@ static bool RAWINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency #ifdef SDL_JOYSTICK_RAWINPUT_WGI // Save off the motor state in case trigger rumble is started - WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; - HRESULT hr; - gamepad_state->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; - gamepad_state->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; + ctx->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; + ctx->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; if (!rumbled && ctx->wgi_correlated) { - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration); + WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration); if (SUCCEEDED(hr)) { rumbled = true; } @@ -1509,12 +1508,11 @@ static bool RAWINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_ #ifdef SDL_JOYSTICK_RAWINPUT_WGI RAWINPUT_DeviceContext *ctx = joystick->hwdata; + ctx->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; + ctx->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; if (ctx->wgi_correlated) { WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; - HRESULT hr; - gamepad_state->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; - gamepad_state->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration); + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration); if (!SUCCEEDED(hr)) { return SDL_SetError("Setting vibration failed: 0x%lx", hr); } From 2248d3812e02e2541c767bf5cf6d37277fbd33a7 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 6 May 2025 20:01:28 -0700 Subject: [PATCH 39/83] joystick: Rework GIP code to allow separate states for individual attachments This is needed for future work bringing up things like the chatpad. This commit also fixes a few minor things, such as still sending motor packets to devices that don't support it, enabling quirks that hide trigger rumble on devices that are marked as not having it, and fixing #12942. --- src/joystick/hidapi/SDL_hidapi_gip.c | 1072 +++++++++++++++----------- src/joystick/usb_ids.h | 1 + 2 files changed, 643 insertions(+), 430 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c index 81a51e10fb..a1f72ce4bf 100644 --- a/src/joystick/hidapi/SDL_hidapi_gip.c +++ b/src/joystick/hidapi/SDL_hidapi_gip.c @@ -38,6 +38,7 @@ #endif #define MAX_MESSAGE_LENGTH 0x4000 +#define MAX_ATTACHMENTS 8 #define GIP_DATA_CLASS_COMMAND (0u << 5) #define GIP_DATA_CLASS_LOW_LATENCY (1u << 5) @@ -216,9 +217,11 @@ #define GIP_FEATURE_SECURITY_OPT_OUT (1u << 4) #define GIP_FEATURE_MOTOR_CONTROL (1u << 5) #define GIP_FEATURE_GUIDE_COLOR (1u << 6) +#define GIP_FEATURE_EXTENDED_SET_DEVICE_STATE (1u << 7) #define GIP_QUIRK_NO_HELLO (1u << 0) #define GIP_QUIRK_BROKEN_METADATA (1u << 1) +#define GIP_QUIRK_NO_IMPULSE_VIBRATION (1u << 2) typedef enum { @@ -240,7 +243,8 @@ typedef enum GIP_TYPE_WHEEL = 2, GIP_TYPE_FLIGHT_STICK = 3, GIP_TYPE_NAVIGATION_CONTROLLER = 4, -} GIP_DeviceType; + GIP_TYPE_CHATPAD = 5, +} GIP_AttachmentType; typedef enum { @@ -272,6 +276,7 @@ SDL_COMPILE_TIME_ASSERT(GUID, sizeof(GUID) == 16); MAKE_GUID(GUID_ArcadeStick, 0x332054cc, 0xa34b, 0x41d5, 0xa3, 0x4a, 0xa6, 0xa6, 0x71, 0x1e, 0xc4, 0xb3); MAKE_GUID(GUID_DynamicLatencyInput, 0x87f2e56b, 0xc3bb, 0x49b1, 0x82, 0x65, 0xff, 0xff, 0xf3, 0x77, 0x99, 0xee); +MAKE_GUID(GUID_FlightStick, 0x03f1a011, 0xefe9, 0x4cc1, 0x96, 0x9c, 0x38, 0xdc, 0x55, 0xf4, 0x04, 0xd0); MAKE_GUID(GUID_IConsoleFunctionMap_InputReport, 0xecddd2fe, 0xd387, 0x4294, 0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d); MAKE_GUID(GUID_IConsoleFunctionMap_OverflowInputReport, 0x137d4bd0, 0x9347, 0x4472, 0xaa, 0x26, 0x8c, 0x34, 0xa0, 0x8f, 0xf9, 0xbd); MAKE_GUID(GUID_IController, 0x9776ff56, 0x9bfd, 0x4581, 0xad, 0x45, 0xb6, 0x45, 0xbb, 0xa5, 0x26, 0xd6); @@ -302,31 +307,32 @@ typedef struct GIP_Quirks { Uint16 vendor_id; Uint16 product_id; + Uint8 attachment_index; Uint32 added_features; Uint32 filtered_features; Uint32 quirks; Uint32 extra_in_system[8]; Uint32 extra_out_system[8]; - GIP_DeviceType device_type; + GIP_AttachmentType device_type; } GIP_Quirks; static const GIP_Quirks quirks[] = { - { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1, + { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1, 0, .added_features = GIP_FEATURE_ELITE_BUTTONS, .filtered_features = GIP_FEATURE_CONSOLE_FUNCTION_MAP }, - { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2, - .added_features = GIP_FEATURE_ELITE_BUTTONS | GIP_FEATURE_DYNAMIC_LATENCY_INPUT | GIP_FEATURE_CONSOLE_FUNCTION_MAP | GIP_FEATURE_GUIDE_COLOR, + { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2, 0, + .added_features = GIP_FEATURE_ELITE_BUTTONS | GIP_FEATURE_DYNAMIC_LATENCY_INPUT | GIP_FEATURE_CONSOLE_FUNCTION_MAP | GIP_FEATURE_GUIDE_COLOR | GIP_FEATURE_EXTENDED_SET_DEVICE_STATE, .extra_in_system = { 1 << GIP_CMD_FIRMWARE }, .extra_out_system = { 1 << GIP_CMD_FIRMWARE } }, - { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_SERIES_X, + { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_SERIES_X, 0, .added_features = GIP_FEATURE_DYNAMIC_LATENCY_INPUT }, - { USB_VENDOR_PDP, USB_PRODUCT_PDP_ROCK_CANDY, + { USB_VENDOR_PDP, USB_PRODUCT_PDP_ROCK_CANDY, 0, .quirks = GIP_QUIRK_NO_HELLO }, - { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_FIGHTPAD, + { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_FIGHTPAD, 0, .filtered_features = GIP_FEATURE_MOTOR_CONTROL }, /* @@ -335,10 +341,10 @@ static const GIP_Quirks quirks[] = { * However, since it just lets us bypass the metadata exchange, let's just * do that instead of having an unreliable init */ - { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_CLASSIC, - .quirks = GIP_QUIRK_BROKEN_METADATA }, + { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_CLASSIC, 0, + .quirks = GIP_QUIRK_BROKEN_METADATA | GIP_QUIRK_NO_IMPULSE_VIBRATION }, - { USB_VENDOR_RAZER, USB_PRODUCT_RAZER_ATROX, + { USB_VENDOR_RAZER, USB_PRODUCT_RAZER_ATROX, 0, .filtered_features = GIP_FEATURE_MOTOR_CONTROL, .device_type = GIP_TYPE_ARCADE_STICK }, @@ -374,7 +380,7 @@ typedef struct GIP_DeviceMetadata GUID *supported_interfaces; Uint8 *hid_descriptor; - GIP_DeviceType device_type; + GIP_AttachmentType device_type; } GIP_DeviceMetadata; typedef struct GIP_MessageMetadata @@ -398,9 +404,12 @@ typedef struct GIP_Metadata GIP_MessageMetadata *message_metadata; } GIP_Metadata; -typedef struct GIP_Device +struct GIP_Device; +typedef struct GIP_Attachment { - SDL_HIDAPI_Device *device; + struct GIP_Device *device; + Uint8 attachment_index; + SDL_JoystickID joystick; Uint8 fragment_message; Uint16 total_length; @@ -412,9 +421,6 @@ typedef struct GIP_Device Uint16 firmware_major_version; Uint16 firmware_minor_version; - Uint64 hello_deadline; - bool got_hello; - GIP_MetadataStatus got_metadata; Uint64 metadata_next; int metadata_retries; @@ -438,14 +444,24 @@ typedef struct GIP_Device Uint8 last_input[64]; - bool reset_for_metadata; - GIP_DeviceType device_type; + GIP_AttachmentType attachment_type; GIP_PaddleFormat paddle_format; Uint32 features; Uint32 quirks; Uint8 share_button_idx; Uint8 paddle_idx; int paddle_offset; +} GIP_Attachment; + +typedef struct GIP_Device +{ + SDL_HIDAPI_Device *device; + + Uint64 hello_deadline; + bool got_hello; + bool reset_for_metadata; + + GIP_Attachment *attachments[MAX_ATTACHMENTS]; } GIP_Device; typedef struct GIP_HelloDevice @@ -509,7 +525,7 @@ typedef struct GIP_InitialReportsRequest Uint8 data[2]; } GIP_InitialReportsRequest; -static bool GIP_SetMetadataDefaults(GIP_Device *device); +static bool GIP_SetMetadataDefaults(GIP_Attachment *attachment); static int GIP_DecodeLength(Uint64 *length, const Uint8 *bytes, int num_bytes) { @@ -546,23 +562,26 @@ static int GIP_EncodeLength(Uint64 length, Uint8 *bytes, int num_bytes) return offset; } -static bool GIP_SupportsSystemMessage(GIP_Device *device, Uint8 command, bool upstream) +static bool GIP_SupportsSystemMessage(GIP_Attachment *attachment, Uint8 command, bool upstream) { if (upstream) { - return device->metadata.device.in_system_messages[command >> 5] & (1u << command); + return attachment->metadata.device.in_system_messages[command >> 5] & (1u << command); } else { - return device->metadata.device.out_system_messages[command >> 5] & (1u << command); + return attachment->metadata.device.out_system_messages[command >> 5] & (1u << command); } } -static bool GIP_SupportsVendorMessage(GIP_Device *device, Uint8 command, bool upstream) +static bool GIP_SupportsVendorMessage(GIP_Attachment *attachment, Uint8 command, bool upstream) { size_t i; - for (i = 0; i < device->metadata.num_messages; i++) { - GIP_MessageMetadata *metadata = &device->metadata.message_metadata[i]; + for (i = 0; i < attachment->metadata.num_messages; i++) { + GIP_MessageMetadata *metadata = &attachment->metadata.message_metadata[i]; if (metadata->type != command) { continue; } + if (metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE) { + return true; + } if (upstream) { return metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM; } else { @@ -572,34 +591,34 @@ static bool GIP_SupportsVendorMessage(GIP_Device *device, Uint8 command, bool up return false; } -static Uint8 GIP_SequenceNext(GIP_Device *device, Uint8 command, bool system) +static Uint8 GIP_SequenceNext(GIP_Attachment *attachment, Uint8 command, bool system) { Uint8 seq; if (system) { switch (command) { case GIP_CMD_SECURITY: - seq = device->seq_security++; + seq = attachment->seq_security++; if (!seq) { - seq = device->seq_security++; + seq = attachment->seq_security++; } break; case GIP_CMD_EXTENDED: - seq = device->seq_extended++; + seq = attachment->seq_extended++; if (!seq) { - seq = device->seq_extended++; + seq = attachment->seq_extended++; } break; case GIP_AUDIO_DATA: - seq = device->seq_audio++; + seq = attachment->seq_audio++; if (!seq) { - seq = device->seq_audio++; + seq = attachment->seq_audio++; } break; default: - seq = device->seq_system++; + seq = attachment->seq_system++; if (!seq) { - seq = device->seq_system++; + seq = attachment->seq_system++; } break; } @@ -609,32 +628,35 @@ static Uint8 GIP_SequenceNext(GIP_Device *device, Uint8 command, bool system) return 0; } - seq = device->seq_vendor++; + seq = attachment->seq_vendor++; if (!seq) { - seq = device->seq_vendor++; + seq = attachment->seq_vendor++; } } return seq; } -static void GIP_HandleQuirks(GIP_Device *device) +static void GIP_HandleQuirks(GIP_Attachment *attachment) { size_t i, j; for (i = 0; quirks[i].vendor_id; i++) { - if (quirks[i].vendor_id != device->device->vendor_id) { + if (quirks[i].vendor_id != attachment->device->device->vendor_id) { continue; } - if (quirks[i].product_id != device->device->product_id) { + if (quirks[i].product_id != attachment->device->device->product_id) { continue; } - device->features |= quirks[i].added_features; - device->features &= ~quirks[i].filtered_features; - device->quirks = quirks[i].quirks; - device->device_type = quirks[i].device_type; + if (quirks[i].attachment_index != attachment->attachment_index) { + continue; + } + attachment->features |= quirks[i].added_features; + attachment->features &= ~quirks[i].filtered_features; + attachment->quirks = quirks[i].quirks; + attachment->attachment_type = quirks[i].device_type; for (j = 0; j < 8; ++j) { - device->metadata.device.in_system_messages[j] |= quirks[i].extra_in_system[j]; - device->metadata.device.out_system_messages[j] |= quirks[i].extra_out_system[j]; + attachment->metadata.device.in_system_messages[j] |= quirks[i].extra_in_system[j]; + attachment->metadata.device.out_system_messages[j] |= quirks[i].extra_out_system[j]; } break; } @@ -687,16 +709,16 @@ static bool GIP_SendRawMessage( } static bool GIP_SendSystemMessage( - GIP_Device *device, + GIP_Attachment *attachment, Uint8 message_type, Uint8 flags, const Uint8 *bytes, int num_bytes) { - return GIP_SendRawMessage(device, + return GIP_SendRawMessage(attachment->device, message_type, - GIP_FLAG_SYSTEM | flags, - GIP_SequenceNext(device, message_type, true), + GIP_FLAG_SYSTEM | attachment->attachment_index | flags, + GIP_SequenceNext(attachment, message_type, true), bytes, num_bytes, true, @@ -705,16 +727,16 @@ static bool GIP_SendSystemMessage( } static bool GIP_SendVendorMessage( - GIP_Device *device, + GIP_Attachment *attachment, Uint8 message_type, Uint8 flags, const Uint8 *bytes, int num_bytes) { - return GIP_SendRawMessage(device, + return GIP_SendRawMessage(attachment->device, message_type, flags, - GIP_SequenceNext(device, message_type, false), + GIP_SequenceNext(attachment, message_type, false), bytes, num_bytes, true, @@ -798,6 +820,11 @@ static bool GIP_ParseDeviceMetadata(GIP_Metadata *metadata, const Uint8 *bytes, for (i = 0; i < count; i++) { Uint8 message = bytes[buffer_offset + 1 + i]; +#ifdef DEBUG_XBOX_PROTOCOL + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "GIP: Supported upstream system message %02x", + message); +#endif device->in_system_messages[message >> 5] |= 1u << (message & 0x1F); } } @@ -815,6 +842,11 @@ static bool GIP_ParseDeviceMetadata(GIP_Metadata *metadata, const Uint8 *bytes, for (i = 0; i < count; i++) { Uint8 message = bytes[buffer_offset + 1 + i]; +#ifdef DEBUG_XBOX_PROTOCOL + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "GIP: Supported downstream system message %02x", + message); +#endif device->out_system_messages[message >> 5] |= 1u << (message & 0x1F); } } @@ -919,9 +951,16 @@ static bool GIP_ParseMessageMetadata(GIP_MessageMetadata *metadata, const Uint8 #ifdef DEBUG_XBOX_PROTOCOL SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, - "GIP: Supported vendor message type %02x of length %d", + "GIP: Supported vendor message type %02x of length %d, %s, %s, %s", metadata->type, - metadata->length); + metadata->length, + metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM ? + (metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "bidirectional" : "upstream") : + metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "downstream" : + metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE ? "downstream request response" : + "unknown direction", + metadata->flags & GIP_MESSAGE_FLAG_SEQUENCED ? "sequenced" : "not sequenced", + metadata->flags & GIP_MESSAGE_FLAG_RELIABLE ? "reliable" : "unreliable"); #endif *offset += length; @@ -1014,24 +1053,25 @@ static bool GIP_Acknowledge( NULL, NULL); } -static bool GIP_FragmentFailed(GIP_Device *device, const GIP_Header *header) { - device->fragment_retries++; - if (device->fragment_retries > 8) { - if (device->fragment_data) { - SDL_free(device->fragment_data); - device->fragment_data = NULL; + +static bool GIP_FragmentFailed(GIP_Attachment *attachment, const GIP_Header *header) { + attachment->fragment_retries++; + if (attachment->fragment_retries > 8) { + if (attachment->fragment_data) { + SDL_free(attachment->fragment_data); + attachment->fragment_data = NULL; } - device->fragment_message = 0; + attachment->fragment_message = 0; } - return GIP_Acknowledge(device, + return GIP_Acknowledge(attachment->device, header, - device->fragment_offset, - (Uint16) (device->total_length - device->fragment_offset)); + attachment->fragment_offset, + (Uint16) (attachment->total_length - attachment->fragment_offset)); } -static bool GIP_EnableEliteButtons(GIP_Device *device) { - if (device->paddle_format == GIP_PADDLES_XBE2_RAW || - (device->firmware_major_version != 4 && device->firmware_minor_version < 17)) +static bool GIP_EnableEliteButtons(GIP_Attachment *attachment) { + if (attachment->paddle_format == GIP_PADDLES_XBE2_RAW || + (attachment->firmware_major_version != 4 && attachment->firmware_minor_version < 17)) { /* * The meaning of this packet is unknown and not documented, but it's @@ -1039,7 +1079,7 @@ static bool GIP_EnableEliteButtons(GIP_Device *device) { */ static const Uint8 enable_raw_report[] = { 7, 0 }; - if (!GIP_SendVendorMessage(device, + if (!GIP_SendVendorMessage(attachment, GIP_SL_ELITE_CONFIG, 0, enable_raw_report, @@ -1052,7 +1092,7 @@ static bool GIP_EnableEliteButtons(GIP_Device *device) { return true; } -static bool GIP_SendGuideButtonLED(GIP_Device *device, Uint8 pattern, Uint8 intensity) +static bool GIP_SendGuideButtonLED(GIP_Attachment *attachment, Uint8 pattern, Uint8 intensity) { Uint8 buffer[] = { GIP_LED_GUIDE, @@ -1060,36 +1100,40 @@ static bool GIP_SendGuideButtonLED(GIP_Device *device, Uint8 pattern, Uint8 inte intensity, }; - return GIP_SendSystemMessage(device, GIP_CMD_LED, 0, buffer, sizeof(buffer)); + if (!GIP_SupportsSystemMessage(attachment, GIP_CMD_LED, false)) { + return true; + } + return GIP_SendSystemMessage(attachment, GIP_CMD_LED, 0, buffer, sizeof(buffer)); } -static bool GIP_SendQueryFirmware(GIP_Device *device, Uint8 slot) +static bool GIP_SendQueryFirmware(GIP_Attachment *attachment, Uint8 slot) { /* The "slot" variable might not be correct; the packet format is still unclear */ Uint8 buffer[] = { 0x1, slot, 0, 0, 0 }; - return GIP_SendSystemMessage(device, GIP_CMD_FIRMWARE, 0, buffer, sizeof(buffer)); + return GIP_SendSystemMessage(attachment, GIP_CMD_FIRMWARE, 0, buffer, sizeof(buffer)); } -static bool GIP_SendSetDeviceState(GIP_Device *device, Uint8 state, Uint8 attachment) +static bool GIP_SendSetDeviceState(GIP_Attachment *attachment, Uint8 state) { Uint8 buffer[] = { state }; - attachment &= GIP_FLAG_ATTACHMENT_MASK; - return GIP_SendSystemMessage(device, GIP_CMD_SET_DEVICE_STATE, attachment, buffer, sizeof(buffer)); + return GIP_SendSystemMessage(attachment, + GIP_CMD_SET_DEVICE_STATE, + attachment->attachment_index, + buffer, + sizeof(buffer)); } -static bool GIP_SendInitSequence(GIP_Device *device) +static bool GIP_SendInitSequence(GIP_Attachment *attachment) { - if (device->device->vendor_id == USB_VENDOR_MICROSOFT && - device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) - { + if (attachment->features & GIP_FEATURE_EXTENDED_SET_DEVICE_STATE) { /* * The meaning of this packet is unknown and not documented, but it's * needed for the Elite 2 controller to start up on older firmwares */ static const Uint8 set_device_state[] = { GIP_STATE_UNK6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x55, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; - if (!GIP_SendSystemMessage(device, + if (!GIP_SendSystemMessage(attachment, GIP_CMD_SET_DEVICE_STATE, 0, set_device_state, @@ -1098,83 +1142,91 @@ static bool GIP_SendInitSequence(GIP_Device *device) return false; } - if (!GIP_EnableEliteButtons(device)) { + if (!GIP_EnableEliteButtons(attachment)) { return false; } } - if (!GIP_SendSetDeviceState(device, GIP_STATE_START, 0)) { + if (!GIP_SendSetDeviceState(attachment, GIP_STATE_START)) { return false; } - device->device_state = GIP_STATE_START; + attachment->device_state = GIP_STATE_START; - if (!GIP_SendGuideButtonLED(device, GIP_LED_GUIDE_ON, 20)) { + if (!GIP_SendGuideButtonLED(attachment, GIP_LED_GUIDE_ON, 20)) { return false; } - if (GIP_SupportsSystemMessage(device, GIP_CMD_SECURITY, false) && - !(device->features & GIP_FEATURE_SECURITY_OPT_OUT)) + if (GIP_SupportsSystemMessage(attachment, GIP_CMD_SECURITY, false) && + !(attachment->features & GIP_FEATURE_SECURITY_OPT_OUT)) { /* TODO: Implement Security command property */ Uint8 buffer[] = { 0x1, 0x0 }; - GIP_SendSystemMessage(device, GIP_CMD_SECURITY, 0, buffer, sizeof(buffer)); + GIP_SendSystemMessage(attachment, GIP_CMD_SECURITY, 0, buffer, sizeof(buffer)); } - if (GIP_SupportsVendorMessage(device, GIP_CMD_INITIAL_REPORTS_REQUEST, false)) { + if (GIP_SupportsVendorMessage(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, false)) { GIP_InitialReportsRequest request = { 0 }; - GIP_SendVendorMessage(device, GIP_CMD_INITIAL_REPORTS_REQUEST, 0, (const Uint8 *)&request, sizeof(request)); + GIP_SendVendorMessage(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, 0, (const Uint8 *)&request, sizeof(request)); } - return HIDAPI_JoystickConnected(device->device, NULL); + + if (!attachment->joystick) { + return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick); + } + return true; } -static bool GIP_EnsureMetadata(GIP_Device *device) +static bool GIP_EnsureMetadata(GIP_Attachment *attachment) { - - switch (device->got_metadata) { + switch (attachment->got_metadata) { case GIP_METADATA_GOT: case GIP_METADATA_FAKED: return true; case GIP_METADATA_NONE: - if (device->quirks & GIP_QUIRK_BROKEN_METADATA) { - GIP_SendSystemMessage(device, GIP_CMD_METADATA, 0, NULL, 0); - GIP_SetMetadataDefaults(device); - return GIP_SendInitSequence(device); - } else if (device->got_hello) { - device->got_metadata = GIP_METADATA_PENDING; - device->metadata_next = SDL_GetTicks() + 500; - device->metadata_retries = 0; - return GIP_SendSystemMessage(device, GIP_CMD_METADATA, 0, NULL, 0); + if (attachment->quirks & GIP_QUIRK_BROKEN_METADATA) { + GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); + GIP_SetMetadataDefaults(attachment); + return GIP_SendInitSequence(attachment); + } else if (attachment->device->got_hello) { + attachment->got_metadata = GIP_METADATA_PENDING; + attachment->metadata_next = SDL_GetTicks() + 500; + attachment->metadata_retries = 0; + return GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); } else { - return GIP_SetMetadataDefaults(device); + return GIP_SetMetadataDefaults(attachment); } default: return true; } } -static bool GIP_SetMetadataDefaults(GIP_Device *device) +static bool GIP_SetMetadataDefaults(GIP_Attachment *attachment) { - /* Some decent default settings */ - device->features |= GIP_FEATURE_MOTOR_CONTROL; - device->device_type = GIP_TYPE_GAMEPAD; - device->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_GUIDE_BUTTON); + if (attachment->attachment_index == 0) { + /* Some decent default settings */ + attachment->features |= GIP_FEATURE_MOTOR_CONTROL; + attachment->attachment_type = GIP_TYPE_GAMEPAD; + attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_GUIDE_BUTTON); - if (SDL_IsJoystickXboxSeriesX(device->device->vendor_id, device->device->product_id)) { - device->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP; + if (SDL_IsJoystickXboxSeriesX(attachment->device->device->vendor_id, attachment->device->device->product_id)) { + attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP; + } } - GIP_HandleQuirks(device); + GIP_HandleQuirks(attachment); - if (GIP_SupportsSystemMessage(device, GIP_CMD_FIRMWARE, false)) { - GIP_SendQueryFirmware(device, 2); + if (GIP_SupportsSystemMessage(attachment, GIP_CMD_FIRMWARE, false)) { + GIP_SendQueryFirmware(attachment, 2); } - device->got_metadata = GIP_METADATA_FAKED; - device->hello_deadline = 0; - return HIDAPI_JoystickConnected(device->device, NULL); + attachment->got_metadata = GIP_METADATA_FAKED; + attachment->device->hello_deadline = 0; + if (!attachment->joystick) { + return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick); + } + return true; } static bool GIP_HandleCommandProtocolControl( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1185,7 +1237,7 @@ static bool GIP_HandleCommandProtocolControl( } static bool GIP_HandleCommandHelloDevice( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1268,28 +1320,30 @@ static bool GIP_HandleCommandHelloDevice( } if (header->flags & GIP_FLAG_ATTACHMENT_MASK) { - return GIP_SendSystemMessage(device, GIP_CMD_METADATA, header->flags & GIP_FLAG_ATTACHMENT_MASK, NULL, 0); + return GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); } else { - device->firmware_major_version = message.firmware_major_version; - device->firmware_minor_version = message.firmware_minor_version; + attachment->firmware_major_version = message.firmware_major_version; + attachment->firmware_minor_version = message.firmware_minor_version; - device->hello_deadline = 0; - device->got_hello = true; - if (device->got_metadata == GIP_METADATA_FAKED) { - device->got_metadata = GIP_METADATA_NONE; + if (attachment->attachment_index == 0) { + attachment->device->hello_deadline = 0; + attachment->device->got_hello = true; } - GIP_EnsureMetadata(device); + if (attachment->got_metadata == GIP_METADATA_FAKED) { + attachment->got_metadata = GIP_METADATA_NONE; + } + GIP_EnsureMetadata(attachment); } return true; } static bool GIP_HandleCommandStatusDevice( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) { - GIP_ExtendedStatus status = {0}; + GIP_ExtendedStatus status = {{0}}; int i; if (num_bytes < 1) { @@ -1332,12 +1386,12 @@ static bool GIP_HandleCommandStatusDevice( } } - GIP_EnsureMetadata(device); + GIP_EnsureMetadata(attachment); return true; } static bool GIP_HandleCommandMetadataRespose( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1348,68 +1402,70 @@ static bool GIP_HandleCommandMetadataRespose( bool found_controller_guid = false; int i; - if (header->flags & GIP_FLAG_ATTACHMENT_MASK) { - /* TODO: Parse properly */ - return true; - } - if (!GIP_ParseMetadata(&metadata, bytes, num_bytes)) { return false; } - if (device->got_metadata == GIP_METADATA_GOT) { - GIP_MetadataFree(&device->metadata); + if (attachment->got_metadata == GIP_METADATA_GOT) { + GIP_MetadataFree(&attachment->metadata); } - device->metadata = metadata; - device->got_metadata = GIP_METADATA_GOT; - device->features = 0; + attachment->metadata = metadata; + attachment->got_metadata = GIP_METADATA_GOT; + attachment->features = 0; - device->device_type = GIP_TYPE_UNKNOWN; + attachment->attachment_type = GIP_TYPE_UNKNOWN; +#ifdef DEBUG_XBOX_PROTOCOL for (i = 0; i < metadata.device.num_preferred_types; i++) { const char *type = metadata.device.preferred_types[i]; -#ifdef DEBUG_XBOX_PROTOCOL SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Device preferred type: %s", type); + } #endif + for (i = 0; i < metadata.device.num_preferred_types; i++) { + const char *type = metadata.device.preferred_types[i]; if (SDL_strcmp(type, "Windows.Xbox.Input.Gamepad") == 0) { - device->device_type = GIP_TYPE_GAMEPAD; + attachment->attachment_type = GIP_TYPE_GAMEPAD; expected_guid = &GUID_IGamepad; break; } if (SDL_strcmp(type, "Microsoft.Xbox.Input.ArcadeStick") == 0) { - device->device_type = GIP_TYPE_ARCADE_STICK; + attachment->attachment_type = GIP_TYPE_ARCADE_STICK; expected_guid = &GUID_ArcadeStick; break; } if (SDL_strcmp(type, "Windows.Xbox.Input.ArcadeStick") == 0) { - device->device_type = GIP_TYPE_ARCADE_STICK; + attachment->attachment_type = GIP_TYPE_ARCADE_STICK; expected_guid = &GUID_ArcadeStick; break; } if (SDL_strcmp(type, "Microsoft.Xbox.Input.FlightStick") == 0) { - device->device_type = GIP_TYPE_FLIGHT_STICK; - expected_guid = &GUID_ArcadeStick; + attachment->attachment_type = GIP_TYPE_FLIGHT_STICK; + expected_guid = &GUID_FlightStick; break; } if (SDL_strcmp(type, "Windows.Xbox.Input.FlightStick") == 0) { - device->device_type = GIP_TYPE_FLIGHT_STICK; - expected_guid = &GUID_ArcadeStick; + attachment->attachment_type = GIP_TYPE_FLIGHT_STICK; + expected_guid = &GUID_FlightStick; break; } if (SDL_strcmp(type, "Microsoft.Xbox.Input.Wheel") == 0) { - device->device_type = GIP_TYPE_WHEEL; + attachment->attachment_type = GIP_TYPE_WHEEL; expected_guid = &GUID_Wheel; break; } if (SDL_strcmp(type, "Windows.Xbox.Input.Wheel") == 0) { - device->device_type = GIP_TYPE_WHEEL; + attachment->attachment_type = GIP_TYPE_WHEEL; expected_guid = &GUID_Wheel; break; } if (SDL_strcmp(type, "Windows.Xbox.Input.NavigationController") == 0) { - device->device_type = GIP_TYPE_NAVIGATION_CONTROLLER; + attachment->attachment_type = GIP_TYPE_NAVIGATION_CONTROLLER; expected_guid = &GUID_NavigationController; break; } + if (SDL_strcmp(type, "Windows.Xbox.Input.Chatpad") == 0) { + attachment->attachment_type = GIP_TYPE_CHATPAD; + break; + } } for (i = 0; i < metadata.device.num_supported_interfaces; i++) { @@ -1428,23 +1484,23 @@ static bool GIP_HandleCommandMetadataRespose( continue; } if (SDL_memcmp(&GUID_IDevAuthPCOptOut, guid, sizeof(GUID)) == 0) { - device->features |= GIP_FEATURE_SECURITY_OPT_OUT; + attachment->features |= GIP_FEATURE_SECURITY_OPT_OUT; continue; } if (SDL_memcmp(&GUID_IConsoleFunctionMap_InputReport, guid, sizeof(GUID)) == 0) { - device->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP; + attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP; continue; } if (SDL_memcmp(&GUID_IConsoleFunctionMap_OverflowInputReport, guid, sizeof(GUID)) == 0) { - device->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW; + attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW; continue; } if (SDL_memcmp(&GUID_IEliteButtons, guid, sizeof(GUID)) == 0) { - device->features |= GIP_FEATURE_ELITE_BUTTONS; + attachment->features |= GIP_FEATURE_ELITE_BUTTONS; continue; } if (SDL_memcmp(&GUID_DynamicLatencyInput, guid, sizeof(GUID)) == 0) { - device->features |= GIP_FEATURE_DYNAMIC_LATENCY_INPUT; + attachment->features |= GIP_FEATURE_DYNAMIC_LATENCY_INPUT; continue; } } @@ -1453,7 +1509,7 @@ static bool GIP_HandleCommandMetadataRespose( GIP_MessageMetadata *message = &metadata.message_metadata[i]; if (message->type == GIP_CMD_DIRECT_MOTOR && message->length >= 9 && (message->flags & GIP_MESSAGE_FLAG_DOWNSTREAM)) { - device->features |= GIP_FEATURE_MOTOR_CONTROL; + attachment->features |= GIP_FEATURE_MOTOR_CONTROL; } } @@ -1462,17 +1518,19 @@ static bool GIP_HandleCommandMetadataRespose( "GIP: Controller was missing expected GUID. This controller probably won't work on an actual Xbox."); } - if ((device->features & GIP_CMD_GUIDE_COLOR) && !GIP_SupportsVendorMessage(device, GIP_CMD_GUIDE_COLOR, false)) { - device->features &= ~GIP_CMD_GUIDE_COLOR; + if ((attachment->features & GIP_CMD_GUIDE_COLOR) && + !GIP_SupportsVendorMessage(attachment, GIP_CMD_GUIDE_COLOR, false)) + { + attachment->features &= ~GIP_CMD_GUIDE_COLOR; } - GIP_HandleQuirks(device); + GIP_HandleQuirks(attachment); - return GIP_SendInitSequence(device); + return GIP_SendInitSequence(attachment); } static bool GIP_HandleCommandSecurity( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1483,7 +1541,7 @@ static bool GIP_HandleCommandSecurity( } static bool GIP_HandleCommandGuideButtonStatus( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1491,11 +1549,11 @@ static bool GIP_HandleCommandGuideButtonStatus( Uint64 timestamp = SDL_GetTicksNS(); SDL_Joystick *joystick = NULL; - if (device->device->num_joysticks < 1) { + if (attachment->device->device->num_joysticks < 1) { return true; } - joystick = SDL_GetJoystickFromID(device->device->joysticks[0]); + joystick = SDL_GetJoystickFromID(attachment->joystick); if (!joystick) { return false; } @@ -1507,7 +1565,7 @@ static bool GIP_HandleCommandGuideButtonStatus( } static bool GIP_HandleCommandAudioControl( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1518,7 +1576,7 @@ static bool GIP_HandleCommandAudioControl( } static bool GIP_HandleCommandFirmware( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1545,20 +1603,20 @@ static bool GIP_HandleCommandFirmware( SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Firmware version: %d.%d.%d rev %d", major, minor, build, rev); - device->firmware_major_version = major; - device->firmware_minor_version = minor; + attachment->firmware_major_version = major; + attachment->firmware_minor_version = minor; - if (device->device->vendor_id == USB_VENDOR_MICROSOFT && - device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) + if (attachment->device->device->vendor_id == USB_VENDOR_MICROSOFT && + attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) { - if (device->firmware_major_version == 5 && device->firmware_minor_version < 17) { - device->paddle_format = GIP_PADDLES_XBE2_RAW; + if (attachment->firmware_major_version == 5 && attachment->firmware_minor_version < 17) { + attachment->paddle_format = GIP_PADDLES_XBE2_RAW; } else { - device->paddle_format = GIP_PADDLES_XBE2; + attachment->paddle_format = GIP_PADDLES_XBE2; } } - return GIP_EnableEliteButtons(device); + return GIP_EnableEliteButtons(attachment); } else { SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Firmware message"); @@ -1567,7 +1625,7 @@ static bool GIP_HandleCommandFirmware( } static bool GIP_HandleCommandRawReport( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1575,44 +1633,44 @@ static bool GIP_HandleCommandRawReport( Uint64 timestamp = SDL_GetTicksNS(); SDL_Joystick *joystick = NULL; - if (device->device->num_joysticks < 1) { + if (attachment->device->device->num_joysticks < 1) { return true; } - joystick = SDL_GetJoystickFromID(device->device->joysticks[0]); + joystick = SDL_GetJoystickFromID(attachment->joystick); if (!joystick) { - return false; + return true; } - if (num_bytes < 17 || num_bytes <= device->paddle_offset) { + if (num_bytes < 17 || num_bytes <= attachment->paddle_offset) { SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short raw report"); return false; } - if ((device->features & GIP_FEATURE_ELITE_BUTTONS) && device->paddle_format == GIP_PADDLES_XBE2_RAW) { + if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS) && attachment->paddle_format == GIP_PADDLES_XBE2_RAW) { SDL_SendJoystickButton(timestamp, joystick, - device->paddle_idx, - (bytes[device->paddle_offset] & 0x01) != 0); + attachment->paddle_idx, + (bytes[attachment->paddle_offset] & 0x01) != 0); SDL_SendJoystickButton(timestamp, joystick, - device->paddle_idx + 1, - (bytes[device->paddle_offset] & 0x02) != 0); + attachment->paddle_idx + 1, + (bytes[attachment->paddle_offset] & 0x02) != 0); SDL_SendJoystickButton(timestamp, joystick, - device->paddle_idx + 2, - (bytes[device->paddle_offset] & 0x04) != 0); + attachment->paddle_idx + 2, + (bytes[attachment->paddle_offset] & 0x04) != 0); SDL_SendJoystickButton(timestamp, joystick, - device->paddle_idx + 3, - (bytes[device->paddle_offset] & 0x08) != 0); + attachment->paddle_idx + 3, + (bytes[attachment->paddle_offset] & 0x08) != 0); } return true; } static bool GIP_HandleCommandHidReport( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1623,7 +1681,7 @@ static bool GIP_HandleCommandHidReport( } static bool GIP_HandleCommandExtended( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1639,8 +1697,11 @@ static bool GIP_HandleCommandExtended( if (bytes[1] != GIP_EXTENDED_STATUS_OK) { return true; } + if (header->flags & GIP_FLAG_ATTACHMENT_MASK) { + return true; + } SDL_memcpy(serial, &bytes[2], SDL_min(sizeof(serial) - 1, num_bytes - 2)); - HIDAPI_SetDeviceSerial(device->device, serial); + HIDAPI_SetDeviceSerial(attachment->device->device, serial); break; default: // TODO @@ -1651,39 +1712,14 @@ static bool GIP_HandleCommandExtended( return true; } -static bool GIP_HandleLLInputReport( - GIP_Device *device, - const GIP_Header *header, +static void GIP_HandleNavigationReport( + GIP_Attachment *attachment, + SDL_Joystick *joystick, + Uint64 timestamp, const Uint8 *bytes, int num_bytes) { - Sint16 axis; - Uint64 timestamp = SDL_GetTicksNS(); - SDL_Joystick *joystick = NULL; - - if (device->device->num_joysticks < 1) { - GIP_EnsureMetadata(device); - if (device->got_metadata != GIP_METADATA_GOT && device->got_metadata != GIP_METADATA_FAKED) { - return true; - } - } - - joystick = SDL_GetJoystickFromID(device->device->joysticks[0]); - if (!joystick) { - return false; - } - - if (device->device_state != GIP_STATE_START) { - SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding early input report"); - device->device_state = GIP_STATE_START; - return true; - } - - if (num_bytes < 14) { - SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short input report"); - return false; - } - if (device->last_input[0] != bytes[0]) { + if (attachment->last_input[0] != bytes[0]) { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((bytes[0] & 0x04) != 0)); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((bytes[0] & 0x08) != 0)); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((bytes[0] & 0x10) != 0)); @@ -1692,7 +1728,7 @@ static bool GIP_HandleLLInputReport( SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((bytes[0] & 0x80) != 0)); } - if (device->last_input[1] != bytes[1]) { + if (attachment->last_input[1] != bytes[1]) { Uint8 hat = 0; if (bytes[1] & 0x01) { @@ -1709,7 +1745,7 @@ static bool GIP_HandleLLInputReport( } SDL_SendJoystickHat(timestamp, joystick, 0, hat); - if (device->device_type == GIP_TYPE_ARCADE_STICK) { + if (attachment->attachment_type == GIP_TYPE_ARCADE_STICK) { /* Previous */ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((bytes[1] & 0x10) != 0)); /* Next */ @@ -1721,7 +1757,16 @@ static bool GIP_HandleLLInputReport( SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((bytes[1] & 0x80) != 0)); } } +} +static void GIP_HandleGamepadReport( + GIP_Attachment *attachment, + SDL_Joystick *joystick, + Uint64 timestamp, + const Uint8 *bytes, + int num_bytes) +{ + Sint16 axis; axis = bytes[2]; axis |= bytes[3] << 8; axis = SDL_clamp(axis, 0, 1023); @@ -1740,72 +1785,144 @@ static bool GIP_HandleLLInputReport( } SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); - if (device->device_type == GIP_TYPE_ARCADE_STICK) { + axis = bytes[6]; + axis |= bytes[7] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = bytes[8]; + axis |= bytes[9] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); + axis = bytes[10]; + axis |= bytes[11] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = bytes[12]; + axis |= bytes[13] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); +} + +static void GIP_HandleArcadeStickReport( + GIP_Attachment *attachment, + SDL_Joystick *joystick, + Uint64 timestamp, + const Uint8 *bytes, + int num_bytes) +{ + Sint16 axis; + axis = bytes[2]; + axis |= bytes[3] << 8; + axis = SDL_clamp(axis, 0, 1023); + axis = (axis - 512) * 64; + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + axis = bytes[4]; + axis |= bytes[5] << 8; + axis = SDL_clamp(axis, 0, 1023); + axis = (axis - 512) * 64; + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + if (num_bytes >= 19) { /* Extra button 6 */ SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (bytes[18] & 0x40) ? 32767 : -32768); /* Extra button 7 */ SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (bytes[18] & 0x80) ? 32767 : -32768); - } else { - axis = bytes[6]; - axis |= bytes[7] << 8; - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); - axis = bytes[8]; - axis |= bytes[9] << 8; - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); - axis = bytes[10]; - axis |= bytes[11] << 8; - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); - axis = bytes[12]; - axis |= bytes[13] << 8; - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); } +} - if ((device->features & GIP_FEATURE_ELITE_BUTTONS) && - num_bytes > device->paddle_offset && - device->last_input[device->paddle_offset] != bytes[device->paddle_offset]) - { - if (device->paddle_format == GIP_PADDLES_XBE1) { - if (bytes[device->paddle_offset] & 0x10) { - SDL_SendJoystickButton(timestamp, - joystick, - device->paddle_idx, - (bytes[device->paddle_offset] & 0x02) != 0); - SDL_SendJoystickButton(timestamp, - joystick, - device->paddle_idx + 1, - (bytes[device->paddle_offset] & 0x08) != 0); - SDL_SendJoystickButton(timestamp, - joystick, - device->paddle_idx + 2, - (bytes[device->paddle_offset] & 0x01) != 0); - SDL_SendJoystickButton(timestamp, - joystick, - device->paddle_idx + 3, - (bytes[device->paddle_offset] & 0x04) != 0); - } - } else if (device->paddle_format == GIP_PADDLES_XBE2) { - SDL_SendJoystickButton(timestamp, - joystick, - device->paddle_idx, - (bytes[device->paddle_offset] & 0x01) != 0); - SDL_SendJoystickButton(timestamp, - joystick, - device->paddle_idx + 1, - (bytes[device->paddle_offset] & 0x02) != 0); - SDL_SendJoystickButton(timestamp, - joystick, - device->paddle_idx + 2, - (bytes[device->paddle_offset] & 0x04) != 0); - SDL_SendJoystickButton(timestamp, - joystick, - device->paddle_idx + 3, - (bytes[device->paddle_offset] & 0x08) != 0); +static bool GIP_HandleLLInputReport( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + Uint64 timestamp = SDL_GetTicksNS(); + SDL_Joystick *joystick = NULL; + + if (attachment->device->device->num_joysticks < 1) { + GIP_EnsureMetadata(attachment); + if (attachment->got_metadata != GIP_METADATA_GOT && attachment->got_metadata != GIP_METADATA_FAKED) { + return true; } } - if ((device->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) && num_bytes >= 32) { + joystick = SDL_GetJoystickFromID(attachment->joystick); + if (!joystick) { + return false; + } + + if (attachment->device_state != GIP_STATE_START) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding early input report"); + attachment->device_state = GIP_STATE_START; + return true; + } + + if (num_bytes < 14) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short input report"); + return false; + } + + GIP_HandleNavigationReport(attachment, joystick, timestamp, bytes, num_bytes); + + switch (attachment->attachment_type) { + case GIP_TYPE_GAMEPAD: + default: + GIP_HandleGamepadReport(attachment, joystick, timestamp, bytes, num_bytes); + break; + case GIP_TYPE_ARCADE_STICK: + GIP_HandleArcadeStickReport(attachment, joystick, timestamp, bytes, num_bytes); + break; + } + + if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS) && + num_bytes > attachment->paddle_offset && + attachment->last_input[attachment->paddle_offset] != bytes[attachment->paddle_offset]) + { + if (attachment->paddle_format == GIP_PADDLES_XBE1) { + if (bytes[attachment->paddle_offset] & 0x10) { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx, + (bytes[attachment->paddle_offset] & 0x02) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 1, + (bytes[attachment->paddle_offset] & 0x08) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 2, + (bytes[attachment->paddle_offset] & 0x01) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 3, + (bytes[attachment->paddle_offset] & 0x04) != 0); + } + } else if (attachment->paddle_format == GIP_PADDLES_XBE2) { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx, + (bytes[attachment->paddle_offset] & 0x01) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 1, + (bytes[attachment->paddle_offset] & 0x02) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 2, + (bytes[attachment->paddle_offset] & 0x04) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 3, + (bytes[attachment->paddle_offset] & 0x08) != 0); + } + } + + if ((attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) && num_bytes >= 32) { int function_map_offset = -1; - if (device->features & GIP_FEATURE_DYNAMIC_LATENCY_INPUT) { + if (attachment->features & GIP_FEATURE_DYNAMIC_LATENCY_INPUT) { /* The dynamic latency input bytes are after the console function map */ if (num_bytes >= 40) { function_map_offset = num_bytes - 26; @@ -1814,22 +1931,22 @@ static bool GIP_HandleLLInputReport( function_map_offset = num_bytes - 18; } if (function_map_offset >= 14) { - if (device->last_input[function_map_offset] != bytes[function_map_offset]) { + if (attachment->last_input[function_map_offset] != bytes[function_map_offset]) { SDL_SendJoystickButton(timestamp, joystick, - device->share_button_idx, + attachment->share_button_idx, (bytes[function_map_offset] & 0x01) != 0); } } } - SDL_memcpy(device->last_input, bytes, SDL_min(num_bytes, sizeof(device->last_input))); + SDL_memcpy(attachment->last_input, bytes, SDL_min(num_bytes, sizeof(attachment->last_input))); return true; } static bool GIP_HandleLLStaticConfiguration( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1840,7 +1957,7 @@ static bool GIP_HandleLLStaticConfiguration( } static bool GIP_HandleLLButtonInfoReport( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1851,7 +1968,7 @@ static bool GIP_HandleLLButtonInfoReport( } static bool GIP_HandleLLOverflowInputReport( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1862,7 +1979,7 @@ static bool GIP_HandleLLOverflowInputReport( } static bool GIP_HandleAudioData( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1873,12 +1990,12 @@ static bool GIP_HandleAudioData( } static bool GIP_HandleSystemMessage( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) { - if (!GIP_SupportsSystemMessage(device, header->message_type, true)) { + if (!GIP_SupportsSystemMessage(attachment, header->message_type, true)) { SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received claimed-unsupported system message type %02x", header->message_type); @@ -1886,27 +2003,27 @@ static bool GIP_HandleSystemMessage( } switch (header->message_type) { case GIP_CMD_PROTO_CONTROL: - return GIP_HandleCommandProtocolControl(device, header, bytes, num_bytes); + return GIP_HandleCommandProtocolControl(attachment, header, bytes, num_bytes); case GIP_CMD_HELLO_DEVICE: - return GIP_HandleCommandHelloDevice(device, header, bytes, num_bytes); + return GIP_HandleCommandHelloDevice(attachment, header, bytes, num_bytes); case GIP_CMD_STATUS_DEVICE: - return GIP_HandleCommandStatusDevice(device, header, bytes, num_bytes); + return GIP_HandleCommandStatusDevice(attachment, header, bytes, num_bytes); case GIP_CMD_METADATA: - return GIP_HandleCommandMetadataRespose(device, header, bytes, num_bytes); + return GIP_HandleCommandMetadataRespose(attachment, header, bytes, num_bytes); case GIP_CMD_SECURITY: - return GIP_HandleCommandSecurity(device, header, bytes, num_bytes); + return GIP_HandleCommandSecurity(attachment, header, bytes, num_bytes); case GIP_CMD_GUIDE_BUTTON: - return GIP_HandleCommandGuideButtonStatus(device, header, bytes, num_bytes); + return GIP_HandleCommandGuideButtonStatus(attachment, header, bytes, num_bytes); case GIP_CMD_AUDIO_CONTROL: - return GIP_HandleCommandAudioControl(device, header, bytes, num_bytes); + return GIP_HandleCommandAudioControl(attachment, header, bytes, num_bytes); case GIP_CMD_FIRMWARE: - return GIP_HandleCommandFirmware(device, header, bytes, num_bytes); + return GIP_HandleCommandFirmware(attachment, header, bytes, num_bytes); case GIP_CMD_HID_REPORT: - return GIP_HandleCommandHidReport(device, header, bytes, num_bytes); + return GIP_HandleCommandHidReport(attachment, header, bytes, num_bytes); case GIP_CMD_EXTENDED: - return GIP_HandleCommandExtended(device, header, bytes, num_bytes); + return GIP_HandleCommandExtended(attachment, header, bytes, num_bytes); case GIP_AUDIO_DATA: - return GIP_HandleAudioData(device, header, bytes, num_bytes); + return GIP_HandleAudioData(attachment, header, bytes, num_bytes); default: SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received unknown system message type %02x", @@ -1915,29 +2032,43 @@ static bool GIP_HandleSystemMessage( } } +static GIP_Attachment * GIP_EnsureAttachment(GIP_Device *device, Uint8 attachment_index) +{ + GIP_Attachment *attachment = device->attachments[attachment_index]; + if (!attachment) { + attachment = SDL_calloc(1, sizeof(*attachment)); + attachment->attachment_index = attachment_index; + attachment->device = device; + attachment->metadata.device.in_system_messages[0] = GIP_DEFAULT_IN_SYSTEM_MESSAGES; + attachment->metadata.device.out_system_messages[0] = GIP_DEFAULT_OUT_SYSTEM_MESSAGES; + device->attachments[attachment_index] = attachment; + } + return attachment; +} + static bool GIP_HandleMessage( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) { if (header->flags & GIP_FLAG_SYSTEM) { - return GIP_HandleSystemMessage(device, header, bytes, num_bytes); + return GIP_HandleSystemMessage(attachment, header, bytes, num_bytes); } else { switch (header->message_type) { case GIP_CMD_RAW_REPORT: - if (device->features & GIP_FEATURE_ELITE_BUTTONS) { - return GIP_HandleCommandRawReport(device, header, bytes, num_bytes); + if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) { + return GIP_HandleCommandRawReport(attachment, header, bytes, num_bytes); } break; case GIP_LL_INPUT_REPORT: - return GIP_HandleLLInputReport(device, header, bytes, num_bytes); + return GIP_HandleLLInputReport(attachment, header, bytes, num_bytes); case GIP_LL_STATIC_CONFIGURATION: - return GIP_HandleLLStaticConfiguration(device, header, bytes, num_bytes); + return GIP_HandleLLStaticConfiguration(attachment, header, bytes, num_bytes); case GIP_LL_BUTTON_INFO_REPORT: - return GIP_HandleLLButtonInfoReport(device, header, bytes, num_bytes); + return GIP_HandleLLButtonInfoReport(attachment, header, bytes, num_bytes); case GIP_LL_OVERFLOW_INPUT_REPORT: - return GIP_HandleLLOverflowInputReport(device, header, bytes, num_bytes); + return GIP_HandleLLOverflowInputReport(attachment, header, bytes, num_bytes); } } SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, @@ -1954,6 +2085,8 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt Uint64 fragment_offset = 0; Uint16 bytes_remaining = 0; bool is_fragment; + Uint8 attachment_index; + GIP_Attachment* attachment; if (num_bytes < 5) { return -1; @@ -1965,6 +2098,8 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt offset += GIP_DecodeLength(&header.length, &bytes[offset], num_bytes - offset); is_fragment = header.flags & GIP_FLAG_FRAGMENT; + attachment_index = header.flags & GIP_FLAG_ATTACHMENT_MASK; + attachment = GIP_EnsureAttachment(device, attachment_index); #ifdef DEBUG_XBOX_PROTOCOL HIDAPI_DumpPacket("GIP received message: size = %d", bytes, num_bytes); @@ -1974,23 +2109,23 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt if (is_fragment) { if (header.flags & GIP_FLAG_INIT_FRAG) { Uint64 total_length; - if (device->fragment_message) { + if (attachment->fragment_message) { /* * Reset fragment buffer if we get a new initial * fragment before finishing the last message. * TODO: Is this the correct behavior? */ - if (device->fragment_data) { - SDL_free(device->fragment_data); - device->fragment_data = NULL; + if (attachment->fragment_data) { + SDL_free(attachment->fragment_data); + attachment->fragment_data = NULL; } } offset += GIP_DecodeLength(&total_length, &bytes[offset], num_bytes - offset); if (total_length > MAX_MESSAGE_LENGTH) { return -1; } - device->total_length = (Uint16) total_length; - device->fragment_message = header.message_type; + attachment->total_length = (Uint16) total_length; + attachment->fragment_message = header.message_type; if (header.length > num_bytes - offset) { SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received fragment that claims to be %" SDL_PRIu64 " bytes, expected %i", @@ -2000,55 +2135,55 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt if (header.length > total_length) { SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received too long fragment, %" SDL_PRIu64 " bytes, exceeds %d", - header.length, device->total_length); + header.length, attachment->total_length); return -1; } - device->fragment_data = SDL_malloc(device->total_length); - SDL_memcpy(device->fragment_data, &bytes[offset], (size_t) header.length); + attachment->fragment_data = SDL_malloc(attachment->total_length); + SDL_memcpy(attachment->fragment_data, &bytes[offset], (size_t) header.length); fragment_offset = header.length; - device->fragment_offset = (Uint32) fragment_offset; - bytes_remaining = (Uint16) (device->total_length - fragment_offset); + attachment->fragment_offset = (Uint32) fragment_offset; + bytes_remaining = (Uint16) (attachment->total_length - fragment_offset); } else { - if (header.message_type != device->fragment_message) { + if (header.message_type != attachment->fragment_message) { SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received out of sequence message type %02x, expected %02x", - header.message_type, device->fragment_message); - GIP_FragmentFailed(device, &header); + header.message_type, attachment->fragment_message); + GIP_FragmentFailed(attachment, &header); return -1; } offset += GIP_DecodeLength(&fragment_offset, &bytes[offset], num_bytes - offset); - if (fragment_offset != device->fragment_offset) { + if (fragment_offset != attachment->fragment_offset) { SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received out of sequence fragment, (claimed %" SDL_PRIu64 ", expected %d)", - fragment_offset, device->fragment_offset); + fragment_offset, attachment->fragment_offset); return GIP_Acknowledge(device, &header, - device->fragment_offset, - (Uint16) (device->total_length - device->fragment_offset)); - } else if (fragment_offset + header.length > device->total_length) { + attachment->fragment_offset, + (Uint16) (attachment->total_length - attachment->fragment_offset)); + } else if (fragment_offset + header.length > attachment->total_length) { SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received too long fragment, %" SDL_PRIu64 " exceeds %d", - fragment_offset + header.length, device->total_length); - GIP_FragmentFailed(device, &header); + fragment_offset + header.length, attachment->total_length); + GIP_FragmentFailed(attachment, &header); return -1; } - bytes_remaining = device->total_length - (Uint16) (fragment_offset + header.length); + bytes_remaining = attachment->total_length - (Uint16) (fragment_offset + header.length); if (header.length != 0) { - SDL_memcpy(&device->fragment_data[fragment_offset], &bytes[offset], (size_t) header.length); + SDL_memcpy(&attachment->fragment_data[fragment_offset], &bytes[offset], (size_t) header.length); } else { - ok = GIP_HandleMessage(device, &header, device->fragment_data, device->total_length); - if (device->fragment_data) { - SDL_free(device->fragment_data); - device->fragment_data = NULL; + ok = GIP_HandleMessage(attachment, &header, attachment->fragment_data, attachment->total_length); + if (attachment->fragment_data) { + SDL_free(attachment->fragment_data); + attachment->fragment_data = NULL; } - device->fragment_message = 0; + attachment->fragment_message = 0; } fragment_offset += header.length; - device->fragment_offset = (Uint16) fragment_offset; + attachment->fragment_offset = (Uint16) fragment_offset; } - device->fragment_timer = SDL_GetTicks(); + attachment->fragment_timer = SDL_GetTicks(); } else if (header.length + offset > num_bytes) { SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received message with erroneous length (claimed %" SDL_PRIu64 ", actual %d), discarding", @@ -2058,7 +2193,7 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt num_bytes -= offset; bytes += offset; fragment_offset = header.length; - ok = GIP_HandleMessage(device, &header, bytes, num_bytes); + ok = GIP_HandleMessage(attachment, &header, bytes, num_bytes); } if (ok && (header.flags & GIP_FLAG_ACME)) { @@ -2069,62 +2204,66 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt static void HIDAPI_DriverGIP_RumbleSent(void *userdata) { - GIP_Device *ctx = (GIP_Device *)userdata; + GIP_Attachment *ctx = (GIP_Attachment *)userdata; ctx->rumble_time = SDL_GetTicks(); } -static bool HIDAPI_DriverGIP_UpdateRumble(GIP_Device *device) +static bool HIDAPI_DriverGIP_UpdateRumble(GIP_Attachment *attachment) { GIP_DirectMotor motor; - if (device->rumble_state == GIP_RUMBLE_STATE_QUEUED && device->rumble_time) { - device->rumble_state = GIP_RUMBLE_STATE_BUSY; - } - - if (device->rumble_state == GIP_RUMBLE_STATE_BUSY) { - const int RUMBLE_BUSY_TIME_MS = 10; - if (SDL_GetTicks() >= (device->rumble_time + RUMBLE_BUSY_TIME_MS)) { - device->rumble_time = 0; - device->rumble_state = GIP_RUMBLE_STATE_IDLE; - } - } - - if (!device->rumble_pending) { + if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL)) { return true; } - if (device->rumble_state != GIP_RUMBLE_STATE_IDLE) { + if (attachment->rumble_state == GIP_RUMBLE_STATE_QUEUED && attachment->rumble_time) { + attachment->rumble_state = GIP_RUMBLE_STATE_BUSY; + } + + if (attachment->rumble_state == GIP_RUMBLE_STATE_BUSY) { + const int RUMBLE_BUSY_TIME_MS = 10; + if (SDL_GetTicks() >= (attachment->rumble_time + RUMBLE_BUSY_TIME_MS)) { + attachment->rumble_time = 0; + attachment->rumble_state = GIP_RUMBLE_STATE_IDLE; + } + } + + if (!attachment->rumble_pending) { + return true; + } + + if (attachment->rumble_state != GIP_RUMBLE_STATE_IDLE) { return true; } // We're no longer pending, even if we fail to send the rumble below - device->rumble_pending = false; + attachment->rumble_pending = false; motor.motor_bitmap = GIP_MOTOR_ALL; - motor.left_impulse_level = device->left_impulse_level; - motor.right_impulse_level = device->right_impulse_level; - motor.left_vibration_level = device->left_vibration_level; - motor.right_vibration_level = device->right_vibration_level; + motor.left_impulse_level = attachment->left_impulse_level; + motor.right_impulse_level = attachment->right_impulse_level; + motor.left_vibration_level = attachment->left_vibration_level; + motor.right_vibration_level = attachment->right_vibration_level; motor.duration = SDL_RUMBLE_RESEND_MS / 10 + 5; // Add a 50ms leniency, just in case motor.delay = 0; motor.repeat = 0; Uint8 message[9] = {0}; SDL_memcpy(&message[1], &motor, sizeof(motor)); - if (!GIP_SendRawMessage(device, + if (!GIP_SendRawMessage(attachment->device, GIP_CMD_DIRECT_MOTOR, - 0, - GIP_SequenceNext(device, GIP_CMD_DIRECT_MOTOR, false), + attachment->attachment_index, + GIP_SequenceNext(attachment, GIP_CMD_DIRECT_MOTOR, false), message, sizeof(message), true, HIDAPI_DriverGIP_RumbleSent, - device)) + attachment)) { return SDL_SetError("Couldn't send rumble packet"); } - device->rumble_state = GIP_RUMBLE_STATE_QUEUED; + attachment->rumble_state = GIP_RUMBLE_STATE_QUEUED; return true; } @@ -2169,20 +2308,21 @@ static bool HIDAPI_DriverGIP_IsSupportedDevice(SDL_HIDAPI_Device *device, const static bool HIDAPI_DriverGIP_InitDevice(SDL_HIDAPI_Device *device) { GIP_Device *ctx; + GIP_Attachment *attachment; ctx = (GIP_Device *)SDL_calloc(1, sizeof(*ctx)); if (!ctx) { return false; } ctx->device = device; - ctx->metadata.device.in_system_messages[0] = GIP_DEFAULT_IN_SYSTEM_MESSAGES; - ctx->metadata.device.out_system_messages[0] = GIP_DEFAULT_OUT_SYSTEM_MESSAGES; ctx->reset_for_metadata = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, false); - GIP_HandleQuirks(ctx); - if (ctx->quirks & GIP_QUIRK_NO_HELLO) { + attachment = GIP_EnsureAttachment(ctx, 0); + GIP_HandleQuirks(attachment); + + if (attachment->quirks & GIP_QUIRK_NO_HELLO) { ctx->got_hello = true; - GIP_EnsureMetadata(ctx); + GIP_EnsureMetadata(attachment); } else { ctx->hello_deadline = SDL_GetTicks() + GIP_HELLO_TIMEOUT; } @@ -2202,43 +2342,60 @@ static void HIDAPI_DriverGIP_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL { } -static bool HIDAPI_DriverGIP_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +static GIP_Attachment * HIDAPI_DriverGIP_FindAttachment(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) { GIP_Device *ctx = (GIP_Device *)device->context; + int i; + + for (i = 0; i < MAX_ATTACHMENTS; i++) { + if (ctx->attachments[i] && ctx->attachments[i]->joystick == joystick->instance_id) { + return ctx->attachments[i]; + } + } + return NULL; +} + +static bool HIDAPI_DriverGIP_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + if (!attachment) { + return SDL_SetError("Invalid joystick"); + } SDL_AssertJoysticksLocked(); - ctx->left_impulse_level = 0; - ctx->right_impulse_level = 0; - ctx->left_vibration_level = 0; - ctx->right_vibration_level = 0; - ctx->rumble_state = GIP_RUMBLE_STATE_IDLE; - ctx->rumble_time = 0; - ctx->rumble_pending = false; - SDL_zeroa(ctx->last_input); + attachment->left_impulse_level = 0; + attachment->right_impulse_level = 0; + attachment->left_vibration_level = 0; + attachment->right_vibration_level = 0; + attachment->rumble_state = GIP_RUMBLE_STATE_IDLE; + attachment->rumble_time = 0; + attachment->rumble_pending = false; + SDL_zeroa(attachment->last_input); // Initialize the joystick capabilities joystick->nbuttons = 11; if (device->vendor_id == USB_VENDOR_MICROSOFT) { if (device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1) { - ctx->paddle_offset = 28; - ctx->paddle_format = GIP_PADDLES_XBE1; + attachment->paddle_offset = 28; + attachment->paddle_format = GIP_PADDLES_XBE1; } else if (device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) { - ctx->paddle_offset = 14; - ctx->paddle_format = GIP_PADDLES_XBE2; - if (ctx->firmware_major_version == 5 && ctx->firmware_minor_version < 17) { - ctx->paddle_format = GIP_PADDLES_XBE2_RAW; + attachment->paddle_offset = 14; + attachment->paddle_format = GIP_PADDLES_XBE2; + if (attachment->firmware_major_version == 5 && attachment->firmware_minor_version < 17) { + attachment->paddle_format = GIP_PADDLES_XBE2_RAW; } } } - if (ctx->paddle_offset > 0) { - ctx->paddle_idx = (Uint8) joystick->nbuttons; + if (attachment->paddle_offset > 0) { + attachment->paddle_idx = (Uint8) joystick->nbuttons; joystick->nbuttons += 4; } - if (ctx->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) { - ctx->share_button_idx = (Uint8) joystick->nbuttons; + if (attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) { + attachment->share_button_idx = (Uint8) joystick->nbuttons; joystick->nbuttons++; } + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; joystick->nhats = 1; @@ -2247,38 +2404,58 @@ static bool HIDAPI_DriverGIP_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic static bool HIDAPI_DriverGIP_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { - GIP_Device *ctx = (GIP_Device *)device->context; + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + if (!attachment) { + return SDL_SetError("Invalid joystick"); + } + + if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL)) { + return SDL_Unsupported(); + } // Magnitude is 1..100 so scale the 16-bit input here - ctx->left_vibration_level = (Uint8)(low_frequency_rumble / 655); - ctx->right_vibration_level = (Uint8)(high_frequency_rumble / 655); - ctx->rumble_pending = true; + attachment->left_vibration_level = (Uint8)(low_frequency_rumble / 655); + attachment->right_vibration_level = (Uint8)(high_frequency_rumble / 655); + attachment->rumble_pending = true; - return HIDAPI_DriverGIP_UpdateRumble(ctx); + return HIDAPI_DriverGIP_UpdateRumble(attachment); } static bool HIDAPI_DriverGIP_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) { - GIP_Device *ctx = (GIP_Device *)device->context; + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + if (!attachment) { + return SDL_SetError("Invalid joystick"); + } + + if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL) || (attachment->quirks & GIP_QUIRK_NO_IMPULSE_VIBRATION)) { + return SDL_Unsupported(); + } // Magnitude is 1..100 so scale the 16-bit input here - ctx->left_impulse_level = (Uint8)(left_rumble / 655); - ctx->right_impulse_level = (Uint8)(right_rumble / 655); - ctx->rumble_pending = true; + attachment->left_impulse_level = (Uint8)(left_rumble / 655); + attachment->right_impulse_level = (Uint8)(right_rumble / 655); + attachment->rumble_pending = true; - return HIDAPI_DriverGIP_UpdateRumble(ctx); + return HIDAPI_DriverGIP_UpdateRumble(attachment); } static Uint32 HIDAPI_DriverGIP_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) { - GIP_Device *ctx = (GIP_Device *)device->context; + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); Uint32 result = 0; - - if (ctx->features & GIP_FEATURE_MOTOR_CONTROL) { - result |= SDL_JOYSTICK_CAP_RUMBLE | SDL_JOYSTICK_CAP_TRIGGER_RUMBLE; + if (!attachment) { + return 0; } - if (ctx->features & GIP_FEATURE_GUIDE_COLOR) { + if (attachment->features & GIP_FEATURE_MOTOR_CONTROL) { + result |= SDL_JOYSTICK_CAP_RUMBLE; + if (!(attachment->quirks & GIP_QUIRK_NO_IMPULSE_VIBRATION)) { + result |= SDL_JOYSTICK_CAP_TRIGGER_RUMBLE; + } + } + + if (attachment->features & GIP_FEATURE_GUIDE_COLOR) { result |= SDL_JOYSTICK_CAP_RGB_LED; } @@ -2287,10 +2464,14 @@ static Uint32 HIDAPI_DriverGIP_GetJoystickCapabilities(SDL_HIDAPI_Device *device static bool HIDAPI_DriverGIP_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) { - GIP_Device *ctx = (GIP_Device *)device->context; + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); Uint8 buffer[] = { 0x00, 0x00, 0x00, 0x00, 0x00 }; - if (!(ctx->features & GIP_FEATURE_GUIDE_COLOR)) { + if (!attachment) { + return SDL_SetError("Invalid joystick"); + } + + if (!(attachment->features & GIP_FEATURE_GUIDE_COLOR)) { return SDL_Unsupported(); } @@ -2299,7 +2480,7 @@ static bool HIDAPI_DriverGIP_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joyst buffer[3] = green; buffer[4] = blue; - if (!GIP_SendVendorMessage(ctx, GIP_CMD_GUIDE_COLOR, 0, buffer, sizeof(buffer))) { + if (!GIP_SendVendorMessage(attachment, GIP_CMD_GUIDE_COLOR, 0, buffer, sizeof(buffer))) { return SDL_SetError("Couldn't send LED packet"); } return true; @@ -2320,6 +2501,7 @@ static bool HIDAPI_DriverGIP_UpdateDevice(SDL_HIDAPI_Device *device) { GIP_Device *ctx = (GIP_Device *)device->context; Uint8 bytes[USB_PACKET_LENGTH]; + int i; int num_bytes; bool perform_reset = false; Uint64 timestamp; @@ -2332,35 +2514,52 @@ static bool HIDAPI_DriverGIP_UpdateDevice(SDL_HIDAPI_Device *device) } timestamp = SDL_GetTicks(); - if (ctx->fragment_message && timestamp >= ctx->fragment_timer + 1000) { - SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Reliable message transfer failed"); - ctx->fragment_message = 0; - } if (ctx->hello_deadline && timestamp >= ctx->hello_deadline) { ctx->hello_deadline = 0; perform_reset = true; - } else if (ctx->got_metadata == GIP_METADATA_PENDING && timestamp >= ctx->metadata_next && ctx->fragment_message != GIP_CMD_METADATA) { - if (ctx->metadata_retries < 5) { - SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Retrying metadata request"); - ctx->metadata_retries++; - ctx->metadata_next = timestamp + 500; - GIP_SendSystemMessage(ctx, GIP_CMD_METADATA, 0, NULL, 0); - } else { - perform_reset = true; - } } - if (perform_reset) { - if (ctx->reset_for_metadata) { - GIP_SendSetDeviceState(ctx, GIP_STATE_RESET, 0); - } else { - GIP_SetMetadataDefaults(ctx); + for (i = 0; i < MAX_ATTACHMENTS; i++) { + GIP_Attachment *attachment = ctx->attachments[i]; + if (!attachment) { + continue; } + if (attachment->fragment_message && timestamp >= attachment->fragment_timer + 1000) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Reliable message transfer failed"); + attachment->fragment_message = 0; + } + if (!perform_reset && + attachment->got_metadata == GIP_METADATA_PENDING && + timestamp >= attachment->metadata_next && + attachment->fragment_message != GIP_CMD_METADATA) + { + if (attachment->metadata_retries < 5) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Retrying metadata request"); + attachment->metadata_retries++; + attachment->metadata_next = timestamp + 500; + GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); + } else { + perform_reset = true; + } + } + if (perform_reset) { + if (ctx->reset_for_metadata) { + GIP_SendSetDeviceState(attachment, GIP_STATE_RESET); + } else { + GIP_SetMetadataDefaults(attachment); + } + perform_reset = false; + } + HIDAPI_DriverGIP_UpdateRumble(attachment); } - HIDAPI_DriverGIP_UpdateRumble(ctx); if (num_bytes < 0 && device->num_joysticks > 0) { // Read error, device is disconnected - HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + for (i = 0; i < MAX_ATTACHMENTS; i++) { + GIP_Attachment *attachment = ctx->attachments[i]; + if (attachment) { + HIDAPI_JoystickDisconnected(device, attachment->joystick); + } + } } return (num_bytes >= 0); } @@ -2371,8 +2570,21 @@ static void HIDAPI_DriverGIP_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joysti static void HIDAPI_DriverGIP_FreeDevice(SDL_HIDAPI_Device *device) { GIP_Device *context = (GIP_Device *)device->context; + int i; - GIP_MetadataFree(&context->metadata); + for (i = 0; i < MAX_ATTACHMENTS; i++) { + GIP_Attachment *attachment = context->attachments[i]; + if (!attachment) { + continue; + } + if (attachment->fragment_data) { + SDL_free(attachment->fragment_data); + attachment->fragment_data = NULL; + } + GIP_MetadataFree(&attachment->metadata); + SDL_free(attachment); + context->attachments[i] = NULL; + } } SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGIP = { diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index 323283f6ea..32a8c94188 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -102,6 +102,7 @@ #define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103 0x7210 #define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104 0x7214 #define USB_PRODUCT_PDP_ROCK_CANDY 0x0246 +#define USB_PRODUCT_POWERA_MINI 0x541a #define USB_PRODUCT_RAZER_ATROX 0x0a00 #define USB_PRODUCT_RAZER_KITSUNE 0x1012 #define USB_PRODUCT_RAZER_PANTHERA 0x0401 From 72dd79752e268e197eecf7aa6555ddfac9bdfd98 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 6 May 2025 20:11:50 -0700 Subject: [PATCH 40/83] joystick: Add initial support for GIP flight sticks At the moment, only the ThrustMaster T.Flight Hotas One has full support. The documentation says you can query the extra buttons via a specific command, but the stick appears to reject the command. Further investigation is needed for automatically querying this state. --- src/joystick/hidapi/SDL_hidapi_gip.c | 92 ++++++++++++++++++++++++++++ src/joystick/usb_ids.h | 1 + 2 files changed, 93 insertions(+) diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c index a1f72ce4bf..0fe53f7897 100644 --- a/src/joystick/hidapi/SDL_hidapi_gip.c +++ b/src/joystick/hidapi/SDL_hidapi_gip.c @@ -314,6 +314,8 @@ typedef struct GIP_Quirks Uint32 extra_in_system[8]; Uint32 extra_out_system[8]; GIP_AttachmentType device_type; + Uint8 extra_buttons; + Uint8 extra_axes; } GIP_Quirks; static const GIP_Quirks quirks[] = { @@ -348,6 +350,12 @@ static const GIP_Quirks quirks[] = { .filtered_features = GIP_FEATURE_MOTOR_CONTROL, .device_type = GIP_TYPE_ARCADE_STICK }, + { USB_VENDOR_THRUSTMASTER, USB_PRODUCT_THRUSTMASTER_T_FLIGHT_HOTAS_ONE, 0, + .filtered_features = GIP_FEATURE_MOTOR_CONTROL, + .device_type = GIP_TYPE_FLIGHT_STICK, + .extra_buttons = 5, + .extra_axes = 3 }, + {0}, }; @@ -451,6 +459,10 @@ typedef struct GIP_Attachment Uint8 share_button_idx; Uint8 paddle_idx; int paddle_offset; + + Uint8 extra_button_idx; + int extra_buttons; + int extra_axes; } GIP_Attachment; typedef struct GIP_Device @@ -658,6 +670,9 @@ static void GIP_HandleQuirks(GIP_Attachment *attachment) attachment->metadata.device.in_system_messages[j] |= quirks[i].extra_in_system[j]; attachment->metadata.device.out_system_messages[j] |= quirks[i].extra_out_system[j]; } + + attachment->extra_buttons = quirks[i].extra_buttons; + attachment->extra_axes = quirks[i].extra_axes; break; } } @@ -1168,6 +1183,10 @@ static bool GIP_SendInitSequence(GIP_Attachment *attachment) GIP_SendVendorMessage(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, 0, (const Uint8 *)&request, sizeof(request)); } + if (GIP_SupportsVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, false)) { + GIP_SendVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, 0, NULL, 0); + } + if (!attachment->joystick) { return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick); } @@ -1833,6 +1852,67 @@ static void GIP_HandleArcadeStickReport( } } +static void GIP_HandleFlightStickReport( + GIP_Attachment *attachment, + SDL_Joystick *joystick, + Uint64 timestamp, + const Uint8 *bytes, + int num_bytes) +{ + Sint16 axis; + int i; + + if (num_bytes < 19) { + return; + } + + if (attachment->last_input[2] != bytes[2]) { + /* Fire 1 and 2 */ + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((bytes[2] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((bytes[2] & 0x02) != 0)); + } + for (i = 0; i < attachment->extra_buttons;) { + if (attachment->last_input[i / 8 + 3] != bytes[i / 8 + 3]) { + for (; i < attachment->extra_buttons; i++) { + SDL_SendJoystickButton(timestamp, + joystick, + (Uint8) (attachment->extra_button_idx + i), + ((bytes[i / 8 + 3] & (1u << i)) != 0)); + } + } else { + i += 8; + } + } + + /* Roll, pitch and yaw are signed. Throttle and any extra axes are unsigned. All values are full-range. */ + axis = bytes[11]; + axis |= bytes[12] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + + axis = bytes[13]; + axis |= bytes[14] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + + axis = bytes[15]; + axis |= bytes[16] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + + /* There are no more signed values, so skip RIGHTY */ + + axis = (bytes[18] << 8) - 0x8000; + axis |= bytes[17]; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + for (i = 0; i < attachment->extra_axes; i++) { + if (20 + i * 2 >= num_bytes) { + return; + } + axis = (bytes[20 + i * 2] << 8) - 0x8000; + axis |= bytes[19 + i * 2]; + SDL_SendJoystickAxis(timestamp, joystick, (Uint8) (SDL_GAMEPAD_AXIS_RIGHT_TRIGGER + i), axis); + } +} + static bool GIP_HandleLLInputReport( GIP_Attachment *attachment, const GIP_Header *header, @@ -1875,6 +1955,9 @@ static bool GIP_HandleLLInputReport( case GIP_TYPE_ARCADE_STICK: GIP_HandleArcadeStickReport(attachment, joystick, timestamp, bytes, num_bytes); break; + case GIP_TYPE_FLIGHT_STICK: + GIP_HandleFlightStickReport(attachment, joystick, timestamp, bytes, num_bytes); + break; } if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS) && @@ -2395,8 +2478,17 @@ static bool HIDAPI_DriverGIP_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic attachment->share_button_idx = (Uint8) joystick->nbuttons; joystick->nbuttons++; } + if (attachment->extra_buttons > 0) { + attachment->extra_button_idx = (Uint8) joystick->nbuttons; + joystick->nbuttons += attachment->extra_buttons; + } joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + if (attachment->attachment_type == GIP_TYPE_FLIGHT_STICK) { + /* Flight sticks have at least 4 axes, but only 3 are signed values, so we leave RIGHTY unused */ + joystick->naxes += attachment->extra_axes - 1; + } + joystick->nhats = 1; return true; diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index 32a8c94188..c2418e0a37 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -131,6 +131,7 @@ #define USB_PRODUCT_STEALTH_ULTRA_WIRED 0x7073 #define USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER 0x0575 #define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_PS4 0xd00e +#define USB_PRODUCT_THRUSTMASTER_T_FLIGHT_HOTAS_ONE 0xb68c #define USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE 0x1142 #define USB_PRODUCT_VICTRIX_FS_PRO 0x0203 #define USB_PRODUCT_VICTRIX_FS_PRO_V2 0x0207 From 9a9696072826c3a214fe5968121b128e0927fbbe Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 6 May 2025 20:12:20 -0700 Subject: [PATCH 41/83] joystick: Sort out GIP vendor messages into the supported interfaces --- src/joystick/hidapi/SDL_hidapi_gip.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c index 0fe53f7897..f199f5eaa2 100644 --- a/src/joystick/hidapi/SDL_hidapi_gip.c +++ b/src/joystick/hidapi/SDL_hidapi_gip.c @@ -64,14 +64,25 @@ #define GIP_CMD_DEBUG 0x1f #define GIP_AUDIO_DATA 0x60 -/* Vendor messages */ +/* Navigation vendor messages */ #define GIP_CMD_DIRECT_MOTOR 0x09 -#define GIP_CMD_INITIAL_REPORTS_REQUEST 0x0a -#define GIP_CMD_SET_APPLICATION_MEMORY 0x0b #define GIP_LL_INPUT_REPORT 0x20 +#define GIP_LL_OVERFLOW_INPUT_REPORT 0x26 + +/* Wheel and ArcadeStick vendor messages */ +#define GIP_CMD_INITIAL_REPORTS_REQUEST 0x0a #define GIP_LL_STATIC_CONFIGURATION 0x21 #define GIP_LL_BUTTON_INFO_REPORT 0x22 -#define GIP_LL_OVERFLOW_INPUT_REPORT 0x26 + +/* Wheel vendor messages */ +#define GIP_CMD_SET_APPLICATION_MEMORY 0x0b +#define GIP_CMD_SET_EQUATIONS_STATES 0x0c +#define GIP_CMD_SET_EQUATION 0x0d + +/* FlightStick vendor messages */ +#define GIP_CMD_DEVICE_CAPABILITIES 0x00 +#define GIP_CMD_LED_CAPABILITIES 0x01 +#define GIP_CMD_SET_LED_STATE 0x02 /* Undocumented Elite 2 vendor messages */ #define GIP_CMD_RAW_REPORT 0x0c From 9e0edea16faff047760f46b3a995eee2dc88ab94 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 7 May 2025 16:11:23 -0700 Subject: [PATCH 42/83] joystick: Assume GIP gamepads have the LED command if no metadata is present --- src/joystick/hidapi/SDL_hidapi_gip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c index f199f5eaa2..c22bd52b2f 100644 --- a/src/joystick/hidapi/SDL_hidapi_gip.c +++ b/src/joystick/hidapi/SDL_hidapi_gip.c @@ -219,7 +219,7 @@ #define GIP_HELLO_TIMEOUT 2000 #define GIP_DEFAULT_IN_SYSTEM_MESSAGES 0x5e -#define GIP_DEFAULT_OUT_SYSTEM_MESSAGES 0x72 +#define GIP_DEFAULT_OUT_SYSTEM_MESSAGES 0x472 #define GIP_FEATURE_CONSOLE_FUNCTION_MAP (1u << 0) #define GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW (1u << 1) From 82899501b5d69683df1cc1946d756de35070c99c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 7 May 2025 15:34:28 -0700 Subject: [PATCH 43/83] joystick: Fix GIP fast ACK issue By moving the message sending onto the main thread and adding a small timeout we can now ensure the ACKs are sent fast enough to avoid the firmware bugs in these controllers. --- src/joystick/hidapi/SDL_hidapi_gip.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c index c22bd52b2f..62c6548801 100644 --- a/src/joystick/hidapi/SDL_hidapi_gip.c +++ b/src/joystick/hidapi/SDL_hidapi_gip.c @@ -217,6 +217,7 @@ /* Internal constants, not part of protocol */ #define GIP_HELLO_TIMEOUT 2000 +#define GIP_ACME_TIMEOUT 10 #define GIP_DEFAULT_IN_SYSTEM_MESSAGES 0x5e #define GIP_DEFAULT_OUT_SYSTEM_MESSAGES 0x472 @@ -348,14 +349,8 @@ static const GIP_Quirks quirks[] = { { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_FIGHTPAD, 0, .filtered_features = GIP_FEATURE_MOTOR_CONTROL }, - /* - * The controller can attempt to resend the metadata too quickly, but has - * bugs handling reliable message handling if things get out of sync. - * However, since it just lets us bypass the metadata exchange, let's just - * do that instead of having an unreliable init - */ { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_CLASSIC, 0, - .quirks = GIP_QUIRK_BROKEN_METADATA | GIP_QUIRK_NO_IMPULSE_VIBRATION }, + .quirks = GIP_QUIRK_NO_IMPULSE_VIBRATION }, { USB_VENDOR_RAZER, USB_PRODUCT_RAZER_ATROX, 0, .filtered_features = GIP_FEATURE_MOTOR_CONTROL, @@ -483,6 +478,7 @@ typedef struct GIP_Device Uint64 hello_deadline; bool got_hello; bool reset_for_metadata; + int timeout; GIP_Attachment *attachments[MAX_ATTACHMENTS]; } GIP_Device; @@ -747,7 +743,7 @@ static bool GIP_SendSystemMessage( GIP_SequenceNext(attachment, message_type, true), bytes, num_bytes, - true, + false, NULL, NULL); } @@ -1216,6 +1212,7 @@ static bool GIP_EnsureMetadata(GIP_Attachment *attachment) GIP_SetMetadataDefaults(attachment); return GIP_SendInitSequence(attachment); } else if (attachment->device->got_hello) { + attachment->device->timeout = GIP_ACME_TIMEOUT; attachment->got_metadata = GIP_METADATA_PENDING; attachment->metadata_next = SDL_GetTicks() + 500; attachment->metadata_retries = 0; @@ -2609,7 +2606,8 @@ static bool HIDAPI_DriverGIP_UpdateDevice(SDL_HIDAPI_Device *device) bool perform_reset = false; Uint64 timestamp; - while ((num_bytes = SDL_hid_read_timeout(device->dev, bytes, sizeof(bytes), 0)) > 0) { + while ((num_bytes = SDL_hid_read_timeout(device->dev, bytes, sizeof(bytes), ctx->timeout)) > 0) { + ctx->timeout = 0; int parsed = GIP_ReceivePacket(ctx, bytes, num_bytes); if (parsed <= 0) { break; From 955a49c88344d9de45c2570417a017493ed66f62 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 8 May 2025 14:24:30 -0700 Subject: [PATCH 44/83] joystick: Add quirk for no impulse vibration on the Spectra Pro --- src/joystick/hidapi/SDL_hidapi_gip.c | 3 +++ src/joystick/usb_ids.h | 1 + 2 files changed, 4 insertions(+) diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c index 62c6548801..c840fb09ad 100644 --- a/src/joystick/hidapi/SDL_hidapi_gip.c +++ b/src/joystick/hidapi/SDL_hidapi_gip.c @@ -352,6 +352,9 @@ static const GIP_Quirks quirks[] = { { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_CLASSIC, 0, .quirks = GIP_QUIRK_NO_IMPULSE_VIBRATION }, + { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_SPECTRA_PRO, 0, + .quirks = GIP_QUIRK_NO_IMPULSE_VIBRATION }, + { USB_VENDOR_RAZER, USB_PRODUCT_RAZER_ATROX, 0, .filtered_features = GIP_FEATURE_MOTOR_CONTROL, .device_type = GIP_TYPE_ARCADE_STICK }, diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index c2418e0a37..26059aa289 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -72,6 +72,7 @@ #define USB_PRODUCT_BACKBONE_ONE_IOS_PS5 0x0104 #define USB_PRODUCT_BDA_XB1_CLASSIC 0x581a #define USB_PRODUCT_BDA_XB1_FIGHTPAD 0x791a +#define USB_PRODUCT_BDA_XB1_SPECTRA_PRO 0x592a #define USB_PRODUCT_GOOGLE_STADIA_CONTROLLER 0x9400 #define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 0x1843 #define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1844 From 367cf9ba63bcb5303a3c119d22def6a35e0e4ea8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 8 May 2025 14:42:45 -0700 Subject: [PATCH 45/83] joystick: Always continue processing GIP packets This loop breakout was originally from an attempt to parse coalesced packets. Breaking out early does more harm than good, and no devices coalesce packets, so this is unnecessary. --- src/joystick/hidapi/SDL_hidapi_gip.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c index c840fb09ad..e71bb29691 100644 --- a/src/joystick/hidapi/SDL_hidapi_gip.c +++ b/src/joystick/hidapi/SDL_hidapi_gip.c @@ -2171,7 +2171,7 @@ static bool GIP_HandleMessage( return false; } -static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_bytes) +static void GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_bytes) { GIP_Header header; int offset = 3; @@ -2183,7 +2183,7 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt GIP_Attachment* attachment; if (num_bytes < 5) { - return -1; + return; } header.message_type = bytes[0]; @@ -2216,7 +2216,7 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt } offset += GIP_DecodeLength(&total_length, &bytes[offset], num_bytes - offset); if (total_length > MAX_MESSAGE_LENGTH) { - return -1; + return; } attachment->total_length = (Uint16) total_length; attachment->fragment_message = header.message_type; @@ -2224,13 +2224,13 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received fragment that claims to be %" SDL_PRIu64 " bytes, expected %i", header.length, num_bytes - offset); - return -1; + return; } if (header.length > total_length) { SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received too long fragment, %" SDL_PRIu64 " bytes, exceeds %d", header.length, attachment->total_length); - return -1; + return; } attachment->fragment_data = SDL_malloc(attachment->total_length); SDL_memcpy(attachment->fragment_data, &bytes[offset], (size_t) header.length); @@ -2243,7 +2243,7 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt "GIP: Received out of sequence message type %02x, expected %02x", header.message_type, attachment->fragment_message); GIP_FragmentFailed(attachment, &header); - return -1; + return; } offset += GIP_DecodeLength(&fragment_offset, &bytes[offset], num_bytes - offset); @@ -2251,16 +2251,17 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received out of sequence fragment, (claimed %" SDL_PRIu64 ", expected %d)", fragment_offset, attachment->fragment_offset); - return GIP_Acknowledge(device, + GIP_Acknowledge(device, &header, attachment->fragment_offset, (Uint16) (attachment->total_length - attachment->fragment_offset)); + return; } else if (fragment_offset + header.length > attachment->total_length) { SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received too long fragment, %" SDL_PRIu64 " exceeds %d", fragment_offset + header.length, attachment->total_length); GIP_FragmentFailed(attachment, &header); - return -1; + return; } bytes_remaining = attachment->total_length - (Uint16) (fragment_offset + header.length); @@ -2282,7 +2283,7 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received message with erroneous length (claimed %" SDL_PRIu64 ", actual %d), discarding", header.length + offset, num_bytes); - return -1; + return; } else { num_bytes -= offset; bytes += offset; @@ -2293,7 +2294,6 @@ static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_byt if (ok && (header.flags & GIP_FLAG_ACME)) { GIP_Acknowledge(device, &header, (Uint32) fragment_offset, bytes_remaining); } - return offset + (Uint16) header.length; } static void HIDAPI_DriverGIP_RumbleSent(void *userdata) @@ -2611,10 +2611,7 @@ static bool HIDAPI_DriverGIP_UpdateDevice(SDL_HIDAPI_Device *device) while ((num_bytes = SDL_hid_read_timeout(device->dev, bytes, sizeof(bytes), ctx->timeout)) > 0) { ctx->timeout = 0; - int parsed = GIP_ReceivePacket(ctx, bytes, num_bytes); - if (parsed <= 0) { - break; - } + GIP_ReceivePacket(ctx, bytes, num_bytes); } timestamp = SDL_GetTicks(); From 7b3bd8c5387ebb51d919ecdf188ff998dd74f99e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 8 May 2025 14:55:51 -0700 Subject: [PATCH 46/83] joystick: Improve GIP metadata retry This reduces the number of retries, since they almost never help, and always attempts the fallback if metadata fails enough times. --- src/joystick/hidapi/SDL_hidapi_gip.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c index e71bb29691..f17ec628fb 100644 --- a/src/joystick/hidapi/SDL_hidapi_gip.c +++ b/src/joystick/hidapi/SDL_hidapi_gip.c @@ -1210,11 +1210,7 @@ static bool GIP_EnsureMetadata(GIP_Attachment *attachment) case GIP_METADATA_FAKED: return true; case GIP_METADATA_NONE: - if (attachment->quirks & GIP_QUIRK_BROKEN_METADATA) { - GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); - GIP_SetMetadataDefaults(attachment); - return GIP_SendInitSequence(attachment); - } else if (attachment->device->got_hello) { + if (attachment->device->got_hello) { attachment->device->timeout = GIP_ACME_TIMEOUT; attachment->got_metadata = GIP_METADATA_PENDING; attachment->metadata_next = SDL_GetTicks() + 500; @@ -2633,7 +2629,7 @@ static bool HIDAPI_DriverGIP_UpdateDevice(SDL_HIDAPI_Device *device) timestamp >= attachment->metadata_next && attachment->fragment_message != GIP_CMD_METADATA) { - if (attachment->metadata_retries < 5) { + if (attachment->metadata_retries < 3) { SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Retrying metadata request"); attachment->metadata_retries++; attachment->metadata_next = timestamp + 500; @@ -2647,6 +2643,7 @@ static bool HIDAPI_DriverGIP_UpdateDevice(SDL_HIDAPI_Device *device) GIP_SendSetDeviceState(attachment, GIP_STATE_RESET); } else { GIP_SetMetadataDefaults(attachment); + GIP_SendInitSequence(attachment); } perform_reset = false; } From 86b206dadf8ad40e6657fa37db371a0aeff74e9c Mon Sep 17 00:00:00 2001 From: Evan Hemsley <2342303+thatcosmonaut@users.noreply.github.com> Date: Fri, 9 May 2025 21:45:54 -0700 Subject: [PATCH 47/83] GPU: Special case to avoid assert on GenerateMipmaps (#12995) --- src/gpu/SDL_gpu.c | 20 ++++++++++++++++++-- src/gpu/SDL_sysgpu.h | 2 ++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c index 41ca388553..54a66e6b0d 100644 --- a/src/gpu/SDL_gpu.c +++ b/src/gpu/SDL_gpu.c @@ -1756,7 +1756,11 @@ void SDL_BindGPUVertexSamplers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS - CHECK_SAMPLER_TEXTURES + + if (!((CommandBufferCommonHeader*)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) + { + CHECK_SAMPLER_TEXTURES + } } RENDERPASS_DEVICE->BindVertexSamplers( @@ -1836,7 +1840,11 @@ void SDL_BindGPUFragmentSamplers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS - CHECK_SAMPLER_TEXTURES + + if (!((CommandBufferCommonHeader*)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) + { + CHECK_SAMPLER_TEXTURES + } } RENDERPASS_DEVICE->BindFragmentSamplers( @@ -2605,11 +2613,19 @@ void SDL_GenerateMipmapsForGPUTexture( SDL_assert_release(!"GenerateMipmaps texture must be created with SAMPLER and COLOR_TARGET usage flags!"); return; } + + CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; + commandBufferHeader->ignore_render_pass_texture_validation = true; } COMMAND_BUFFER_DEVICE->GenerateMipmaps( command_buffer, texture); + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; + commandBufferHeader->ignore_render_pass_texture_validation = false; + } } void SDL_BlitGPUTexture( diff --git a/src/gpu/SDL_sysgpu.h b/src/gpu/SDL_sysgpu.h index ebc195c29b..ab69d64210 100644 --- a/src/gpu/SDL_sysgpu.h +++ b/src/gpu/SDL_sysgpu.h @@ -66,6 +66,8 @@ typedef struct CommandBufferCommonHeader Pass copy_pass; bool swapchain_texture_acquired; bool submitted; + // used to avoid tripping assert on GenerateMipmaps + bool ignore_render_pass_texture_validation; } CommandBufferCommonHeader; typedef struct TextureCommonHeader From f92843da8347b56a7529bc1f9dfd9b90b1428436 Mon Sep 17 00:00:00 2001 From: SDL Wiki Bot Date: Sat, 10 May 2025 13:38:22 +0000 Subject: [PATCH 48/83] Sync SDL3 wiki -> header [ci skip] --- include/SDL3/SDL_gpu.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 858b0ef318..def7cc7728 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -1691,6 +1691,9 @@ typedef struct SDL_GPUStencilOpState * \since This struct is available since SDL 3.2.0. * * \sa SDL_GPUColorTargetDescription + * \sa SDL_GPUBlendFactor + * \sa SDL_GPUBlendOp + * \sa SDL_GPUColorComponentFlags */ typedef struct SDL_GPUColorTargetBlendState { From e90f7ac4a80adfd1a06a05dc4ce604ed29615149 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Fri, 9 May 2025 17:54:59 +0200 Subject: [PATCH 49/83] Add hid_version and hid_version_str to renamed LIBUSB impl symbols --- src/hidapi/SDL_hidapi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hidapi/SDL_hidapi.c b/src/hidapi/SDL_hidapi.c index 77a9a5bab9..a525f22391 100644 --- a/src/hidapi/SDL_hidapi.c +++ b/src/hidapi/SDL_hidapi.c @@ -807,6 +807,8 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; #define hid_send_feature_report LIBUSB_hid_send_feature_report #define hid_set_nonblocking LIBUSB_hid_set_nonblocking #define hid_write LIBUSB_hid_write +#define hid_version LIBUSB_hid_version +#define hid_version_str LIBUSB_hid_version_str #define input_report LIBUSB_input_report #define make_path LIBUSB_make_path #define new_hid_device LIBUSB_new_hid_device From 3f2226a917a2a3aefb8daabd42d939099f16f28d Mon Sep 17 00:00:00 2001 From: GamesTrap Date: Wed, 7 May 2025 00:16:16 +0200 Subject: [PATCH 50/83] Add progress bar support for Linux --- CMakeLists.txt | 1 + docs/README-wayland.md | 9 ++ src/core/linux/SDL_dbus.c | 1 + src/core/linux/SDL_dbus.h | 1 + src/core/linux/SDL_progressbar.c | 159 +++++++++++++++++++++++++++ src/core/linux/SDL_progressbar.h | 30 +++++ src/video/SDL_video.c | 6 + src/video/wayland/SDL_waylandvideo.c | 4 + src/video/x11/SDL_x11video.c | 4 + 9 files changed, 215 insertions(+) create mode 100644 src/core/linux/SDL_progressbar.c create mode 100644 src/core/linux/SDL_progressbar.h diff --git a/CMakeLists.txt b/CMakeLists.txt index db0eb1cadb..a36d82acdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1736,6 +1736,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_sources( "${SDL3_SOURCE_DIR}/src/core/linux/SDL_dbus.c" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_system_theme.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_progressbar.c" ) endif() diff --git a/docs/README-wayland.md b/docs/README-wayland.md index 75a9b906e1..a3cd06f78a 100644 --- a/docs/README-wayland.md +++ b/docs/README-wayland.md @@ -59,6 +59,15 @@ encounter limitations or behavior that is different from other windowing systems `SDL_APP_ID` hint string, the desktop entry file name should match the application ID. For example, if your application ID is set to `org.my_org.sdl_app`, the desktop entry file should be named `org.my_org.sdl_app.desktop`. +### The application progress bar can't be set via ```SDL_SetWindowProgressState()``` or ```SDL_SetWindowProgressValue()``` + +- Only some Desktop Environemnts support the underlying API. Known compatible DEs: Unity, KDE +- The underlying API requires a desktop entry file, aka a `.desktop` file. + Please see the [Desktop Entry Specification](https://specifications.freedesktop.org/desktop-entry-spec/latest/) for + more information on the format of this file. Note that if your application manually sets the application ID via the + `SDL_APP_ID` hint string, the desktop entry file name should match the application ID. For example, if your + application ID is set to `org.my_org.sdl_app`, the desktop entry file should be named `org.my_org.sdl_app.desktop`. + ### Keyboard grabs don't work when running under XWayland - On GNOME based desktops, the dconf setting `org/gnome/mutter/wayland/xwayland-allow-grabs` must be enabled. diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 226a7f3293..b61a1cd920 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -68,6 +68,7 @@ static bool LoadDBUSSyms(void) SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path); SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *, const char *), message_new_method_call); + SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *), message_new_signal); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, ...), message_append_args); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, va_list), message_append_args_valist); SDL_DBUS_SYM(void (*)(DBusMessage *, DBusMessageIter *), message_iter_init_append); diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index 097bc31eb3..230b20fded 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -67,6 +67,7 @@ typedef struct SDL_DBusContext dbus_bool_t (*message_is_signal)(DBusMessage *, const char *, const char *); dbus_bool_t (*message_has_path)(DBusMessage *, const char *); DBusMessage *(*message_new_method_call)(const char *, const char *, const char *, const char *); + DBusMessage *(*message_new_signal)(const char *, const char *, const char *); dbus_bool_t (*message_append_args)(DBusMessage *, int, ...); dbus_bool_t (*message_append_args_valist)(DBusMessage *, int, va_list); void (*message_iter_init_append)(DBusMessage *, DBusMessageIter *); diff --git a/src/core/linux/SDL_progressbar.c b/src/core/linux/SDL_progressbar.c new file mode 100644 index 0000000000..e50f8361ca --- /dev/null +++ b/src/core/linux/SDL_progressbar.c @@ -0,0 +1,159 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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. +*/ +#include "SDL_progressbar.h" +#include "SDL_internal.h" + +#include "SDL_dbus.h" + +#ifdef SDL_USE_LIBDBUS + +#include + +#include "../unix/SDL_appid.h" + +#define UnityLauncherAPI_DBUS_INTERFACE "com.canonical.Unity.LauncherEntry" +#define UnityLauncherAPI_DBUS_SIGNAL "Update" + +static char *GetDBUSObjectPath() +{ + char *app_id = SDL_strdup(SDL_GetAppID()); + + if (!app_id) { + return NULL; + } + + // Sanitize exe_name to make it a legal D-Bus path element + for (char *p = app_id; *p; ++p) { + if (!SDL_isalnum(*p)) { + *p = '_'; + } + } + + // Ensure it starts with a letter or underscore + if (!SDL_isalpha(app_id[0]) && app_id[0] != '_') { + SDL_memmove(app_id + 1, app_id, SDL_strlen(app_id) + 1); + app_id[0] = '_'; + } + + // Create full path + char path[1024]; + SDL_snprintf(path, sizeof(path), "/org/libsdl/%s_%d", app_id, getpid()); + + SDL_free(app_id); + + return SDL_strdup(path); +} + +static char *GetAppDesktopPath() +{ + const char *desktop_suffix = ".desktop"; + const char *app_id = SDL_GetAppID(); + const size_t desktop_path_total_length = SDL_strlen(app_id) + SDL_strlen(desktop_suffix) + 1; + char *desktop_path = (char *)SDL_malloc(desktop_path_total_length); + if (!desktop_path) { + return NULL; + } + *desktop_path = '\0'; + SDL_strlcat(desktop_path, app_id, desktop_path_total_length); + SDL_strlcat(desktop_path, desktop_suffix, desktop_path_total_length); + + return desktop_path; +} + +static int ShouldShowProgress(SDL_ProgressState progressState) +{ + if (progressState == SDL_PROGRESS_STATE_INVALID || + progressState == SDL_PROGRESS_STATE_NONE) { + return 0; + } + + // Unity LauncherAPI only supports "normal" display of progress + return 1; +} + +bool DBUS_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window *window) +{ + // Signal signature: + // signal com.canonical.Unity.LauncherEntry.Update (in s app_uri, in a{sv} properties) + + SDL_DBusContext *dbus = SDL_DBus_GetContext(); + + if (!dbus || !dbus->session_conn) { + return false; + } + + char *objectPath = GetDBUSObjectPath(); + if (!objectPath) { + return false; + } + + DBusMessage *msg = dbus->message_new_signal(objectPath, UnityLauncherAPI_DBUS_INTERFACE, UnityLauncherAPI_DBUS_SIGNAL); + if (!msg) { + SDL_free(objectPath); + return false; + } + + char *desktop_path = GetAppDesktopPath(); + if (!desktop_path) { + dbus->message_unref(msg); + SDL_free(objectPath); + return false; + } + + const char *progress_visible_str = "progress-visible"; + const char *progress_str = "progress"; + int dbus_type_boolean_str = DBUS_TYPE_BOOLEAN; + int dbus_type_double_str = DBUS_TYPE_DOUBLE; + + const int progress_visible = ShouldShowProgress(window->progress_state); + double progress = (double)window->progress_value; + + DBusMessageIter args, props; + dbus->message_iter_init_append(msg, &args); + dbus->message_iter_append_basic(&args, DBUS_TYPE_STRING, &desktop_path); // Setup app_uri paramter + dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{sv}", &props); // Setup properties parameter + DBusMessageIter key_it, value_it; + // Set progress visible property + dbus->message_iter_open_container(&props, DBUS_TYPE_DICT_ENTRY, NULL, &key_it); + dbus->message_iter_append_basic(&key_it, DBUS_TYPE_STRING, &progress_visible_str); // Append progress-visible key data + dbus->message_iter_open_container(&key_it, DBUS_TYPE_VARIANT, (const char *)&dbus_type_boolean_str, &value_it); + dbus->message_iter_append_basic(&value_it, DBUS_TYPE_BOOLEAN, &progress_visible); // Append progress-visible value data + dbus->message_iter_close_container(&key_it, &value_it); + dbus->message_iter_close_container(&props, &key_it); + // Set progress value property + dbus->message_iter_open_container(&props, DBUS_TYPE_DICT_ENTRY, NULL, &key_it); + dbus->message_iter_append_basic(&key_it, DBUS_TYPE_STRING, &progress_str); // Append progress key data + dbus->message_iter_open_container(&key_it, DBUS_TYPE_VARIANT, (const char *)&dbus_type_double_str, &value_it); + dbus->message_iter_append_basic(&value_it, DBUS_TYPE_DOUBLE, &progress); // Append progress value data + dbus->message_iter_close_container(&key_it, &value_it); + dbus->message_iter_close_container(&props, &key_it); + dbus->message_iter_close_container(&args, &props); + + dbus->connection_send(dbus->session_conn, msg, NULL); + + SDL_free(desktop_path); + dbus->message_unref(msg); + SDL_free(objectPath); + + return true; +} + +#endif // SDL_USE_LIBDBUS diff --git a/src/core/linux/SDL_progressbar.h b/src/core/linux/SDL_progressbar.h new file mode 100644 index 0000000000..da9b815f4f --- /dev/null +++ b/src/core/linux/SDL_progressbar.h @@ -0,0 +1,30 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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. +*/ + +#ifndef SDL_prograssbar_h_ +#define SDL_prograssbar_h_ + +#include "../../video/SDL_sysvideo.h" +#include "SDL_internal.h" + +extern bool DBUS_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window *window); + +#endif // SDL_prograssbar_h_ diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index c9eb1caa75..294fad5d10 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -2266,6 +2266,12 @@ static void SDL_FinishWindowCreation(SDL_Window *window, SDL_WindowFlags flags) SDL_ShowWindow(window); } } + +#if defined(SDL_PLATFORM_LINUX) + // On Linux the progress state is persisted throughout multiple program runs, so reset state on window creation + SDL_SetWindowProgressState(window, SDL_PROGRESS_STATE_NONE); + SDL_SetWindowProgressValue(window, 0.0f); +#endif } static bool SDL_ContextNotSupported(const char *name) diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 516419c281..b615bf7079 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -24,6 +24,7 @@ #ifdef SDL_VIDEO_DRIVER_WAYLAND #include "../../core/linux/SDL_system_theme.h" +#include "../../core/linux/SDL_progressbar.h" #include "../../events/SDL_events_c.h" #include "SDL_waylandclipboard.h" @@ -629,6 +630,9 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols) device->DestroyWindow = Wayland_DestroyWindow; device->SetWindowHitTest = Wayland_SetWindowHitTest; device->FlashWindow = Wayland_FlashWindow; +#ifdef SDL_USE_LIBDBUS + device->ApplyWindowProgress = DBUS_ApplyWindowProgress; +#endif // SDL_USE_LIBDBUS device->HasScreenKeyboardSupport = Wayland_HasScreenKeyboardSupport; device->ShowWindowSystemMenu = Wayland_ShowWindowSystemMenu; device->SyncWindow = Wayland_SyncWindow; diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index f371f519d6..39fc1e278f 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -25,6 +25,7 @@ #include // For getpid() and readlink() #include "../../core/linux/SDL_system_theme.h" +#include "../../core/linux/SDL_progressbar.h" #include "../../events/SDL_keyboard_c.h" #include "../../events/SDL_mouse_c.h" #include "../SDL_pixels_c.h" @@ -204,6 +205,9 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->AcceptDragAndDrop = X11_AcceptDragAndDrop; device->UpdateWindowShape = X11_UpdateWindowShape; device->FlashWindow = X11_FlashWindow; +#ifdef SDL_USE_LIBDBUS + device->ApplyWindowProgress = DBUS_ApplyWindowProgress; +#endif // SDL_USE_LIBDBUS device->ShowWindowSystemMenu = X11_ShowWindowSystemMenu; device->SetWindowFocusable = X11_SetWindowFocusable; device->SyncWindow = X11_SyncWindow; From 6344712b04f463894bd634acec9881fb154b6598 Mon Sep 17 00:00:00 2001 From: dbolin Date: Fri, 9 May 2025 18:14:48 -0700 Subject: [PATCH 51/83] GPU Vulkan: fix for Swapchain Semaphore Reuse --- src/gpu/vulkan/SDL_gpu_vulkan.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index a1e234d6d2..0c8456ba3a 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -703,7 +703,7 @@ typedef struct WindowData // Synchronization primitives VkSemaphore imageAvailableSemaphore[MAX_FRAMES_IN_FLIGHT]; - VkSemaphore renderFinishedSemaphore[MAX_FRAMES_IN_FLIGHT]; + VkSemaphore *renderFinishedSemaphore; SDL_GPUFence *inFlightFences[MAX_FRAMES_IN_FLIGHT]; Uint32 frameCounter; @@ -3164,7 +3164,6 @@ static void VULKAN_INTERNAL_DestroySwapchain( SDL_free(windowData->textureContainers[i].activeTexture->subresources); SDL_free(windowData->textureContainers[i].activeTexture); } - windowData->imageCount = 0; SDL_free(windowData->textureContainers); windowData->textureContainers = NULL; @@ -3193,7 +3192,8 @@ static void VULKAN_INTERNAL_DestroySwapchain( NULL); windowData->imageAvailableSemaphore[i] = VK_NULL_HANDLE; } - + } + for (i = 0; i < windowData->imageCount; i += 1) { if (windowData->renderFinishedSemaphore[i]) { renderer->vkDestroySemaphore( renderer->logicalDevice, @@ -3202,6 +3202,10 @@ static void VULKAN_INTERNAL_DestroySwapchain( windowData->renderFinishedSemaphore[i] = VK_NULL_HANDLE; } } + SDL_free(windowData->renderFinishedSemaphore); + windowData->renderFinishedSemaphore = NULL; + + windowData->imageCount = 0; } static void VULKAN_INTERNAL_DestroyGraphicsPipelineResourceLayout( @@ -4779,6 +4783,12 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } + windowData->inFlightFences[i] = NULL; + } + + windowData->renderFinishedSemaphore = SDL_malloc( + sizeof(VkSemaphore) * windowData->imageCount); + for (i = 0; i < windowData->imageCount; i += 1) { vulkanResult = renderer->vkCreateSemaphore( renderer->logicalDevice, &semaphoreCreateInfo, @@ -4798,8 +4808,6 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( windowData->swapchain = VK_NULL_HANDLE; CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } - - windowData->inFlightFences[i] = NULL; } windowData->needsSwapchainRecreate = false; @@ -10020,7 +10028,7 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( } vulkanCommandBuffer->signalSemaphores[vulkanCommandBuffer->signalSemaphoreCount] = - windowData->renderFinishedSemaphore[windowData->frameCounter]; + windowData->renderFinishedSemaphore[swapchainImageIndex]; vulkanCommandBuffer->signalSemaphoreCount += 1; *swapchainTexture = (SDL_GPUTexture *)swapchainTextureContainer; @@ -10561,7 +10569,7 @@ static bool VULKAN_Submit( presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.pNext = NULL; presentInfo.pWaitSemaphores = - &presentData->windowData->renderFinishedSemaphore[presentData->windowData->frameCounter]; + &presentData->windowData->renderFinishedSemaphore[presentData->swapchainImageIndex]; presentInfo.waitSemaphoreCount = 1; presentInfo.pSwapchains = &presentData->windowData->swapchain; presentInfo.swapchainCount = 1; From 84308e7fba9b59338109f0c0ad49bc7c5107ac06 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Sat, 10 May 2025 15:39:03 -0400 Subject: [PATCH 52/83] x11: Fix the Openbox quirk flag Openbox needs fullscreen size/position event synthesized, but does not send display changed events. --- src/video/x11/SDL_x11video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 39fc1e278f..b5dab99b5f 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -284,7 +284,7 @@ static SDL_VideoDevice *X11_CreateDevice(void) * This is otherwise not wanted, as it can break fullscreen window positioning on multi-monitor configurations. */ if (!X11_CheckCurrentDesktop("openbox")) { - device->device_caps |= VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES; + device->device_caps |= VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS; } data->is_xwayland = X11_IsXWayland(x11_display); From e1066ceea14441a1bcf3fb52cea5d9d395eaef27 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sun, 11 May 2025 10:55:42 -0400 Subject: [PATCH 53/83] Revert "pulseaudio: cleanup TLS every time we finish a threaded-mainloop callback." This reverts commit 3b91017682146b42e95a5bfcefbc9013615cb3b8. This apparently is cleaning up more threads than expected, so this needs a rethink. Fixes #12986. Fixes https://github.com/libsdl-org/sdl2-compat/issues/486 Fixes https://github.com/libsdl-org/sdl2-compat/issues/482 --- src/audio/pulseaudio/SDL_pulseaudio.c | 28 +++++++++------------------ 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 2403c7d0f9..69e8c1a84c 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -269,19 +269,9 @@ static const char *getAppName(void) return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING); } -static void ThreadedMainloopSignal(void) -{ - PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // alert waiting threads to unblock. - - // we need to kill any SDL_SetError state; we didn't create this thread - // so its SDL TLS slot will leak otherwise, so we do this every time - // we're (presumably) losing control of the thread. - SDL_CleanupTLS(); -} - static void OperationStateChangeCallback(pa_operation *o, void *userdata) { - ThreadedMainloopSignal(); // just signal any waiting code, it can look up the details. + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details. } /* This function assume you are holding `mainloop`'s lock. The operation is unref'd in here, assuming @@ -323,7 +313,7 @@ static void DisconnectFromPulseServer(void) static void PulseContextStateChangeCallback(pa_context *context, void *userdata) { - ThreadedMainloopSignal(); // just signal any waiting code, it can look up the details. + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details. } static bool ConnectToPulseServer(void) @@ -410,7 +400,7 @@ static void WriteCallback(pa_stream *p, size_t nbytes, void *userdata) struct SDL_PrivateAudioData *h = (struct SDL_PrivateAudioData *)userdata; //SDL_Log("PULSEAUDIO WRITE CALLBACK! nbytes=%u", (unsigned int) nbytes); h->bytes_requested += nbytes; - ThreadedMainloopSignal(); + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } // This function waits until it is possible to write a full sound buffer @@ -481,7 +471,7 @@ static Uint8 *PULSEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) static void ReadCallback(pa_stream *p, size_t nbytes, void *userdata) { //SDL_Log("PULSEAUDIO READ CALLBACK! nbytes=%u", (unsigned int) nbytes); - ThreadedMainloopSignal(); // the recording code queries what it needs, we just need to signal to end any wait + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // the recording code queries what it needs, we just need to signal to end any wait } static bool PULSEAUDIO_WaitRecordingDevice(SDL_AudioDevice *device) @@ -602,7 +592,7 @@ static void PULSEAUDIO_CloseDevice(SDL_AudioDevice *device) static void PulseStreamStateChangeCallback(pa_stream *stream, void *userdata) { - ThreadedMainloopSignal(); // just signal any waiting code, it can look up the details. + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details. } static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) @@ -803,7 +793,7 @@ static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, if (i) { AddPulseAudioDevice(false, i->description, i->name, i->index, &i->sample_spec); } - ThreadedMainloopSignal(); + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } // This is called when PulseAudio adds a recording ("source") device. @@ -813,7 +803,7 @@ static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_la if (i && (include_monitors || (i->monitor_of_sink == PA_INVALID_INDEX))) { AddPulseAudioDevice(true, i->description, i->name, i->index, &i->sample_spec); } - ThreadedMainloopSignal(); + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } static void ServerInfoCallback(pa_context *c, const pa_server_info *i, void *data) @@ -838,7 +828,7 @@ static void ServerInfoCallback(pa_context *c, const pa_server_info *i, void *dat } } - ThreadedMainloopSignal(); + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } static bool FindAudioDeviceByIndex(SDL_AudioDevice *device, void *userdata) @@ -882,7 +872,7 @@ static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint3 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByIndex, (void *)(uintptr_t)idx)); } } - ThreadedMainloopSignal(); + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } static bool CheckDefaultDevice(const bool changed, char *device_path) From aaa5d70efcd48ab0dd6759ac18964333c8c1a95d Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Sun, 11 May 2025 10:55:16 -0400 Subject: [PATCH 54/83] wayland: Check the cursor visibility flag when updating seat pointers --- src/video/wayland/SDL_waylandmouse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index 5a2a00d02f..a4f354cdb8 100644 --- a/src/video/wayland/SDL_waylandmouse.c +++ b/src/video/wayland/SDL_waylandmouse.c @@ -1121,7 +1121,7 @@ void Wayland_SeatUpdateCursor(SDL_WaylandSeat *seat) SDL_Mouse *mouse = SDL_GetMouse(); SDL_WindowData *pointer_focus = seat->pointer.focus; - if (pointer_focus) { + if (pointer_focus && mouse->cursor_visible) { const bool has_relative_focus = Wayland_SeatHasRelativePointerFocus(seat); if (!seat->display->relative_mode_enabled || !has_relative_focus || !mouse->relative_mode_hide_cursor) { From c6e9d6cc79586832db7e7a2d0f3f0dc8f2353b79 Mon Sep 17 00:00:00 2001 From: ManuBlack <32620529+ManuBlack@users.noreply.github.com> Date: Mon, 12 May 2025 00:54:43 +0300 Subject: [PATCH 55/83] AAudio: Implemented sample frames hint --- src/audio/aaudio/SDL_aaudio.c | 6 ++++++ src/audio/aaudio/SDL_aaudiofuncs.h | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c index 95124546a9..5436be070a 100644 --- a/src/audio/aaudio/SDL_aaudio.c +++ b/src/audio/aaudio/SDL_aaudio.c @@ -308,6 +308,12 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) ctx.AAudioStreamBuilder_setFormat(builder, format); ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq); ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels); + + // If no specific buffer size has been requested, the device will pick the optimal + if(SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) { + ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * device->sample_frames); // AAudio requires that the buffer capacity is at least + ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, device->sample_frames); // twice the size of the data callback buffer size + } const aaudio_direction_t direction = (recording ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); ctx.AAudioStreamBuilder_setDirection(builder, direction); diff --git a/src/audio/aaudio/SDL_aaudiofuncs.h b/src/audio/aaudio/SDL_aaudiofuncs.h index 02988212d5..1d9f71044c 100644 --- a/src/audio/aaudio/SDL_aaudiofuncs.h +++ b/src/audio/aaudio/SDL_aaudiofuncs.h @@ -31,7 +31,7 @@ SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSamplesPerFrame, (AAudioStreamBuild SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aaudio_format_t format)) SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode)) SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction)) -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames)) +SDL_PROC(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames)) SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode)) SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28 SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28 From 2ae341826045ba20cec7dd4ef1fa84326f64b685 Mon Sep 17 00:00:00 2001 From: Dominic Bolin Date: Sun, 11 May 2025 16:11:45 -0700 Subject: [PATCH 56/83] GPU Vulkan: set correct destination usage mode for storage buffer read/write bindings (#13009) --- src/gpu/vulkan/SDL_gpu_vulkan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 0c8456ba3a..8fc3af87ce 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -8157,7 +8157,7 @@ static void VULKAN_BeginComputePass( vulkanCommandBuffer, bufferContainer, storageBufferBindings[i].cycle, - VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ); + VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE); vulkanCommandBuffer->readWriteComputeStorageBuffers[i] = buffer; From 38da39c8c9f17cb10d8da19b92d55d1c978e1101 Mon Sep 17 00:00:00 2001 From: Mike Kosek Date: Mon, 12 May 2025 14:18:54 +0200 Subject: [PATCH 57/83] Added rightx and righty --- src/joystick/SDL_gamepad_db.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/joystick/SDL_gamepad_db.h b/src/joystick/SDL_gamepad_db.h index a654a28ec6..c6ee1a28ff 100644 --- a/src/joystick/SDL_gamepad_db.h +++ b/src/joystick/SDL_gamepad_db.h @@ -215,7 +215,7 @@ static const char *s_GamepadMappings[] = { "03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,", "03000000782300000a10000000000000,Onlive Wireless Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,", "030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,", - "0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,righttrigger:-a5,start:b11,x:b3,y:b4,", + "0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,rightx:a3,righty:a4,righttrigger:-a5,start:b11,x:b3,y:b4,", "03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,", "030000006f0e00000901000000000000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "03000000632500002306000000000000,PS Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", @@ -421,7 +421,7 @@ static const char *s_GamepadMappings[] = { "03000000790000004418000000010000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", "050000007e05000009200000ff070000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "0300000009120000072f000000010000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a2,leftx:a0,lefty:a1,righttrigger:a5,start:b11,x:b3,y:b4,", + "0300000009120000072f000000010000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a2,leftx:a0,lefty:a1,rightx:a3,righty:a4,righttrigger:a5,start:b11,x:b3,y:b4,", "030000006f0e00000901000002010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", "030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", From 9a6f70d75ade1d1d842af2cdc7cd21921a44eef2 Mon Sep 17 00:00:00 2001 From: yunline Date: Mon, 12 May 2025 21:44:05 +0800 Subject: [PATCH 58/83] tray: Fix wrong `fByPositon` parameter of SetMenuItemInfoW in SDL_SetTrayEntryLabel --- src/tray/windows/SDL_tray.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tray/windows/SDL_tray.c b/src/tray/windows/SDL_tray.c index 18008ee298..15021ac798 100644 --- a/src/tray/windows/SDL_tray.c +++ b/src/tray/windows/SDL_tray.c @@ -544,7 +544,7 @@ void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label) mii.dwTypeData = label_w; mii.cch = (UINT) SDL_wcslen(label_w); - if (!SetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, TRUE, &mii)) { + if (!SetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii)) { SDL_SetError("Couldn't update tray entry label"); } From f6c1e81394d9bd97a4b91cc041d3a08518907bce Mon Sep 17 00:00:00 2001 From: Takase <20792268+takase1121@users.noreply.github.com> Date: Tue, 13 May 2025 00:17:21 +0800 Subject: [PATCH 59/83] [Process API] Quoting enhancements (#12946) --- include/SDL3/SDL_process.h | 7 + src/process/SDL_process.c | 8 ++ src/process/windows/SDL_windowsprocess.c | 30 ++-- test/childprocess.c | 10 ++ test/testprocess.c | 171 ++++++++++++++++++++++- 5 files changed, 217 insertions(+), 9 deletions(-) diff --git a/include/SDL3/SDL_process.h b/include/SDL3/SDL_process.h index 0e19bfff1d..b6aac6a931 100644 --- a/include/SDL3/SDL_process.h +++ b/include/SDL3/SDL_process.h @@ -195,6 +195,12 @@ typedef enum SDL_ProcessIO * run in the background. In this case the default input and output is * `SDL_PROCESS_STDIO_NULL` and the exitcode of the process is not * available, and will always be 0. + * - `SDL_PROP_PROCESS_CREATE_CMDLINE_STRING`: a string containing the program + * to run and any parameters. This string is passed directly to + * `CreateProcess` on Windows, and does nothing on other platforms. + * This property is only important if you want to start programs that does + * non-standard command-line processing, and in most cases using + * `SDL_PROP_PROCESS_CREATE_ARGS_POINTER` is sufficient. * * On POSIX platforms, wait() and waitpid(-1, ...) should not be called, and * SIGCHLD should not be ignored or handled because those would prevent SDL @@ -231,6 +237,7 @@ extern SDL_DECLSPEC SDL_Process * SDLCALL SDL_CreateProcessWithProperties(SDL_Pr #define SDL_PROP_PROCESS_CREATE_STDERR_POINTER "SDL.process.create.stderr_source" #define SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN "SDL.process.create.stderr_to_stdout" #define SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN "SDL.process.create.background" +#define SDL_PROP_PROCESS_CREATE_CMDLINE_STRING "SDL.process.create.cmdline" /** * Get the properties associated with a process. diff --git a/src/process/SDL_process.c b/src/process/SDL_process.c index 3ccf5d582b..5db6db49b4 100644 --- a/src/process/SDL_process.c +++ b/src/process/SDL_process.c @@ -45,10 +45,18 @@ SDL_Process *SDL_CreateProcess(const char * const *args, bool pipe_stdio) SDL_Process *SDL_CreateProcessWithProperties(SDL_PropertiesID props) { const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); +#if defined(SDL_PLATFORM_WINDOWS) + const char *cmdline = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, NULL); + if ((!args || !args[0] || !args[0][0]) && (!cmdline || !cmdline[0])) { + SDL_SetError("Either SDL_PROP_PROCESS_CREATE_ARGS_POINTER or SDL_PROP_PROCESS_CREATE_CMDLINE_STRING must be valid"); + return NULL; + } +#else if (!args || !args[0] || !args[0][0]) { SDL_InvalidParamError("SDL_PROP_PROCESS_CREATE_ARGS_POINTER"); return NULL; } +#endif SDL_Process *process = (SDL_Process *)SDL_calloc(1, sizeof(*process)); if (!process) { diff --git a/src/process/windows/SDL_windowsprocess.c b/src/process/windows/SDL_windowsprocess.c index 3e0249ebd8..65d8a394a0 100644 --- a/src/process/windows/SDL_windowsprocess.c +++ b/src/process/windows/SDL_windowsprocess.c @@ -106,9 +106,12 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) len = 0; for (i = 0; args[i]; i++) { const char *a = args[i]; + bool quotes = *a == '\0' || SDL_strpbrk(a, " \r\n\t\v") != NULL; - /* two double quotes to surround an argument with */ - len += 2; + if (quotes) { + /* surround the argument with double quote if it is empty or contains whitespaces */ + len += 2; + } for (; *a; a++) { switch (*a) { @@ -116,8 +119,8 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) len += 2; break; case '\\': - /* only escape backslashes that precede a double quote */ - len += (a[1] == '"' || a[1] == '\0') ? 2 : 1; + /* only escape backslashes that precede a double quote (including the enclosing double quote) */ + len += (a[1] == '"' || (quotes && a[1] == '\0')) ? 2 : 1; break; case ' ': case '^': @@ -149,8 +152,11 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) i_out = 0; for (i = 0; args[i]; i++) { const char *a = args[i]; + bool quotes = *a == '\0' || SDL_strpbrk(a, " \r\n\t\v") != NULL; - result[i_out++] = '"'; + if (quotes) { + result[i_out++] = '"'; + } for (; *a; a++) { switch (*a) { case '"': @@ -163,7 +169,7 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) break; case '\\': result[i_out++] = *a; - if (a[1] == '"' || a[1] == '\0') { + if (a[1] == '"' || (quotes && a[1] == '\0')) { result[i_out++] = *a; } break; @@ -188,7 +194,9 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) break; } } - result[i_out++] = '"'; + if (quotes) { + result[i_out++] = '"'; + } result[i_out++] = ' '; } SDL_assert(i_out == len); @@ -237,6 +245,7 @@ static bool join_env(char **env, LPWSTR *env_out) bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID props) { const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); + const char *cmdline = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, NULL); SDL_Environment *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, SDL_GetEnvironment()); char **envp = NULL; const char *working_directory = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, NULL); @@ -286,7 +295,12 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID security_attributes.bInheritHandle = TRUE; security_attributes.lpSecurityDescriptor = NULL; - if (!join_arguments(args, &createprocess_cmdline)) { + if (cmdline) { + createprocess_cmdline = WIN_UTF8ToString(cmdline); + if (!createprocess_cmdline) { + goto done; + } + } else if (!join_arguments(args, &createprocess_cmdline)) { goto done; } diff --git a/test/childprocess.c b/test/childprocess.c index 772d86d760..69a7cea2da 100644 --- a/test/childprocess.c +++ b/test/childprocess.c @@ -2,6 +2,11 @@ #include #include +#ifdef SDL_PLATFORM_WINDOWS +#include +#include +#endif + #include #include @@ -102,6 +107,11 @@ int main(int argc, char *argv[]) { if (print_arguments) { int print_i; +#ifdef SDL_PLATFORM_WINDOWS + /* reopen stdout as binary to prevent newline conversion */ + _setmode(_fileno(stdout), _O_BINARY); +#endif + for (print_i = 0; i + print_i < argc; print_i++) { fprintf(stdout, "|%d=%s|\r\n", print_i, argv[i + print_i]); } diff --git a/test/testprocess.c b/test/testprocess.c index 425b445151..4d6437d397 100644 --- a/test/testprocess.c +++ b/test/testprocess.c @@ -82,7 +82,7 @@ static int SDLCALL process_testArguments(void *arg) "", " ", "a b c", - "a\tb\tc\t", + "a\tb\tc\t\v\r\n", "\"a b\" c", "'a' 'b' 'c'", "%d%%%s", @@ -965,6 +965,165 @@ cleanup: return TEST_COMPLETED; } +static int process_testWindowsCmdline(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + "--print-arguments", + "--", + "", + " ", + "a b c", + "a\tb\tc\t", + "\"a b\" c", + "'a' 'b' 'c'", + "%d%%%s", + "\\t\\c", + "evil\\", + "a\\b\"c\\", + "\"\\^&|<>%", /* characters with a special meaning */ + NULL + }; + /* this will have the same result as process_args, but escaped in a different way */ + const char *process_cmdline_template = + "%s " + "--print-arguments " + "-- " + "\"\" " + "\" \" " + "a\" \"b\" \"c\t" /* using tab as delimiter */ + "\"a\tb\tc\t\" " + "\"\"\"\"a b\"\"\" c\" " + "\"'a' 'b' 'c'\" " + "%%d%%%%%%s " /* will be passed to sprintf */ + "\\t\\c " + "evil\\ " + "a\\b\"\\\"\"c\\ " + "\\\"\\^&|<>%%"; + char process_cmdline[65535]; + SDL_PropertiesID props; + SDL_Process *process = NULL; + char *buffer; + int exit_code; + int i; + size_t total_read = 0; + +#ifndef SDL_PLATFORM_WINDOWS + SDLTest_AssertPass("SDL_PROP_PROCESS_CREATE_CMDLINE_STRING only works on Windows"); + return TEST_SKIPPED; +#endif + + props = SDL_CreateProperties(); + SDLTest_AssertCheck(props != 0, "SDL_CreateProperties()"); + if (!props) { + goto failed; + } + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, true); + + process = SDL_CreateProcessWithProperties(props); + SDLTest_AssertCheck(process == NULL, "SDL_CreateProcessWithProperties() should fail"); + + SDL_snprintf(process_cmdline, SDL_arraysize(process_cmdline), process_cmdline_template, data->childprocess_path); + SDL_SetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, process_cmdline); + + process = SDL_CreateProcessWithProperties(props); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcessWithProperties()"); + if (!process) { + goto failed; + } + + exit_code = 0xdeadbeef; + buffer = (char *)SDL_ReadProcess(process, &total_read, &exit_code); + SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + if (!buffer) { + goto failed; + } + SDLTest_LogEscapedString("stdout of process: ", buffer, total_read); + + for (i = 3; process_args[i]; i++) { + char line[64]; + SDL_snprintf(line, sizeof(line), "|%d=%s|", i - 3, process_args[i]); + SDLTest_AssertCheck(!!SDL_strstr(buffer, line), "Check %s is in output", line); + } + SDL_free(buffer); + + SDLTest_AssertPass("About to destroy process"); + SDL_DestroyProcess(process); + + return TEST_COMPLETED; + +failed: + SDL_DestroyProcess(process); + return TEST_ABORTED; +} + +static int process_testWindowsCmdlinePrecedence(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + "--print-arguments", + "--", + "argument 1", + NULL + }; + const char *process_cmdline_template = "%s --print-arguments -- \"argument 2\""; + char process_cmdline[65535]; + SDL_PropertiesID props; + SDL_Process *process = NULL; + char *buffer; + int exit_code; + size_t total_read = 0; + +#ifndef SDL_PLATFORM_WINDOWS + SDLTest_AssertPass("SDL_PROP_PROCESS_CREATE_CMDLINE_STRING only works on Windows"); + return TEST_SKIPPED; +#endif + + props = SDL_CreateProperties(); + SDLTest_AssertCheck(props != 0, "SDL_CreateProperties()"); + if (!props) { + goto failed; + } + + SDL_snprintf(process_cmdline, SDL_arraysize(process_cmdline), process_cmdline_template, data->childprocess_path); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)process_args); + SDL_SetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, (const char *)process_cmdline); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, true); + + process = SDL_CreateProcessWithProperties(props); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcessWithProperties()"); + if (!process) { + goto failed; + } + + exit_code = 0xdeadbeef; + buffer = (char *)SDL_ReadProcess(process, &total_read, &exit_code); + SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + if (!buffer) { + goto failed; + } + SDLTest_LogEscapedString("stdout of process: ", buffer, total_read); + SDLTest_AssertCheck(!!SDL_strstr(buffer, "|0=argument 2|"), "Check |0=argument 2| is printed"); + SDL_free(buffer); + + SDLTest_AssertPass("About to destroy process"); + SDL_DestroyProcess(process); + + return TEST_COMPLETED; + +failed: + SDL_DestroyProcess(process); + return TEST_ABORTED; +} + static const SDLTest_TestCaseReference processTestArguments = { process_testArguments, "process_testArguments", "Test passing arguments to child process", TEST_ENABLED }; @@ -1017,6 +1176,14 @@ static const SDLTest_TestCaseReference processTestFileRedirection = { process_testFileRedirection, "process_testFileRedirection", "Test redirection from/to files", TEST_ENABLED }; +static const SDLTest_TestCaseReference processTestWindowsCmdline = { + process_testWindowsCmdline, "process_testWindowsCmdline", "Test passing cmdline directly to CreateProcess", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference processTestWindowsCmdlinePrecedence = { + process_testWindowsCmdlinePrecedence, "process_testWindowsCmdlinePrecedence", "Test SDL_PROP_PROCESS_CREATE_CMDLINE_STRING precedence over SDL_PROP_PROCESS_CREATE_ARGS_POINTER", TEST_ENABLED +}; + static const SDLTest_TestCaseReference *processTests[] = { &processTestArguments, &processTestExitCode, @@ -1031,6 +1198,8 @@ static const SDLTest_TestCaseReference *processTests[] = { &processTestNonExistingExecutable, &processTestBatBadButVulnerability, &processTestFileRedirection, + &processTestWindowsCmdline, + &processTestWindowsCmdlinePrecedence, NULL }; From e2f7c4046ca5f17b46f1a9252343bb9bf979d48c Mon Sep 17 00:00:00 2001 From: SDL Wiki Bot Date: Mon, 12 May 2025 16:18:27 +0000 Subject: [PATCH 60/83] Sync SDL3 wiki -> header [ci skip] --- include/SDL3/SDL_process.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/SDL3/SDL_process.h b/include/SDL3/SDL_process.h index b6aac6a931..57e3afd94c 100644 --- a/include/SDL3/SDL_process.h +++ b/include/SDL3/SDL_process.h @@ -197,8 +197,8 @@ typedef enum SDL_ProcessIO * available, and will always be 0. * - `SDL_PROP_PROCESS_CREATE_CMDLINE_STRING`: a string containing the program * to run and any parameters. This string is passed directly to - * `CreateProcess` on Windows, and does nothing on other platforms. - * This property is only important if you want to start programs that does + * `CreateProcess` on Windows, and does nothing on other platforms. This + * property is only important if you want to start programs that does * non-standard command-line processing, and in most cases using * `SDL_PROP_PROCESS_CREATE_ARGS_POINTER` is sufficient. * From 0a34279578a2cce104efc0d605cd5a51ece25e95 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 12 May 2025 13:53:30 -0400 Subject: [PATCH 61/83] audio: Fix SDL_GetAudioDeviceName() not working with logical devices. Fixes #12977. --- src/audio/SDL_audio.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index f11066c21b..22f8cd943c 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -1523,8 +1523,10 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) { + // bit #1 of devid is set for physical devices and unset for logical. + const bool islogical = !(devid & (1<<1)); const char *result = NULL; - SDL_AudioDevice *device = NULL; + const void *vdev = NULL; if (!SDL_GetCurrentAudioDriver()) { SDL_SetError("Audio subsystem is not initialized"); @@ -1534,10 +1536,14 @@ const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) // remains valid (in case the device is unplugged at the wrong moment), we hold the // device_hash_lock while we copy the string. SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &device); - if (!device) { + SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, &vdev); + if (!vdev) { SDL_SetError("Invalid audio device instance ID"); + } else if (islogical) { + const SDL_LogicalAudioDevice *logdev = (const SDL_LogicalAudioDevice *) vdev; + result = SDL_GetPersistentString(logdev->physical_device->name); } else { + const SDL_AudioDevice *device = (const SDL_AudioDevice *) vdev; result = SDL_GetPersistentString(device->name); } SDL_UnlockRWLock(current_audio.device_hash_lock); From 83d4dce6979185c6be9c5af1183f2ef08c49e46c Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Mon, 12 May 2025 11:43:35 -0400 Subject: [PATCH 62/83] wayland: Remove all window references from seats when destroying a window Compositors typically send keyboard, pointer, touch, and tablet leave events when a window is destroyed, however, this is not guaranteed behavior, and at least one compositor in widespread use doesn't always send pointer leave events immediately upon destroying a window. Ensure that all references held by seats to a focused window are removed before the underlying window surface and structs are destroyed to prevent potential segfaults if the seats are immediately destroyed after the window. --- src/video/wayland/SDL_waylandevents.c | 135 +++++++++++++----------- src/video/wayland/SDL_waylandevents_c.h | 1 + src/video/wayland/SDL_waylandwindow.c | 6 ++ src/video/wayland/SDL_waylandwindow.h | 3 +- 4 files changed, 84 insertions(+), 61 deletions(-) diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 2ed845bcf8..6aa95a5bee 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -90,7 +90,7 @@ // Scoped function declarations static void Wayland_SeatUpdateKeyboardGrab(SDL_WaylandSeat *seat); -struct SDL_WaylandTouchPoint +typedef struct { SDL_TouchID id; wl_fixed_t fx; @@ -98,11 +98,11 @@ struct SDL_WaylandTouchPoint struct wl_surface *surface; struct wl_list link; -}; +} SDL_WaylandTouchPoint; static void Wayland_SeatAddTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface *surface) { - struct SDL_WaylandTouchPoint *tp = SDL_malloc(sizeof(struct SDL_WaylandTouchPoint)); + SDL_WaylandTouchPoint *tp = SDL_malloc(sizeof(SDL_WaylandTouchPoint)); SDL_zerop(tp); tp->id = id; @@ -113,9 +113,37 @@ static void Wayland_SeatAddTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed WAYLAND_wl_list_insert(&seat->touch.points, &tp->link); } +static void Wayland_SeatCancelTouch(SDL_WaylandSeat *seat, SDL_WaylandTouchPoint *tp) +{ + if (tp->surface) { + SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(tp->surface); + + if (window_data) { + const float x = (float)(wl_fixed_to_double(tp->fx) / window_data->current.logical_width); + const float y = (float)(wl_fixed_to_double(tp->fy) / window_data->current.logical_height); + + SDL_SendTouch(0, (SDL_TouchID)(uintptr_t)seat->touch.wl_touch, + (SDL_FingerID)(tp->id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_CANCELED, x, y, 0.0f); + + --window_data->active_touch_count; + + /* If the window currently has mouse focus and has no currently active keyboards, pointers, + * or touch events, then consider mouse focus to be lost. + */ + if (SDL_GetMouseFocus() == window_data->sdlwindow && !window_data->keyboard_focus_count && + !window_data->pointer_focus_count && !window_data->active_touch_count) { + SDL_SetMouseFocus(NULL); + } + } + } + + WAYLAND_wl_list_remove(&tp->link); + SDL_free(tp); +} + static void Wayland_SeatUpdateTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface **surface) { - struct SDL_WaylandTouchPoint *tp; + SDL_WaylandTouchPoint *tp; wl_list_for_each (tp, &seat->touch.points, link) { if (tp->id == id) { @@ -131,7 +159,7 @@ static void Wayland_SeatUpdateTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fi static void Wayland_SeatRemoveTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t *fx, wl_fixed_t *fy, struct wl_surface **surface) { - struct SDL_WaylandTouchPoint *tp; + SDL_WaylandTouchPoint *tp; wl_list_for_each (tp, &seat->touch.points, link) { if (tp->id == id) { @@ -152,23 +180,6 @@ static void Wayland_SeatRemoveTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fi } } -static bool Wayland_SurfaceHasActiveTouches(SDL_VideoData *display, struct wl_surface *surface) -{ - struct SDL_WaylandTouchPoint *tp; - SDL_WaylandSeat *seat; - - // Check all seats for active touches on the surface. - wl_list_for_each (seat, &display->seat_list, link) { - wl_list_for_each (tp, &seat->touch.points, link) { - if (tp->surface == surface) { - return true; - } - } - } - - return false; -} - static void Wayland_GetScaledMouseRect(SDL_Window *window, SDL_Rect *scaled_rect) { SDL_WindowData *window_data = window->internal; @@ -751,7 +762,7 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer, */ SDL_Window *mouse_focus = SDL_GetMouseFocus(); const bool had_focus = mouse_focus && window->sdlwindow == mouse_focus; - if (!--window->pointer_focus_count && had_focus && !Wayland_SurfaceHasActiveTouches(seat->display, surface)) { + if (!--window->pointer_focus_count && had_focus && !window->active_touch_count) { SDL_SetMouseFocus(NULL); } @@ -1243,6 +1254,7 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri y = (float)wl_fixed_to_double(fy) / (window_data->current.logical_height - 1); } + ++window_data->active_touch_count; SDL_SetMouseFocus(window_data->sdlwindow); SDL_SendTouch(Wayland_GetTouchTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch, @@ -1269,11 +1281,13 @@ static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial SDL_SendTouch(Wayland_GetTouchTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch, (SDL_FingerID)(id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_UP, x, y, 0.0f); - /* If the window currently has mouse focus, the keyboard focus is another window or NULL, the window has no - * pointers active on it, and the surface has no active touch events, then consider mouse focus to be lost. + --window_data->active_touch_count; + + /* If the window currently has mouse focus and has no currently active keyboards, pointers, + * or touch events, then consider mouse focus to be lost. */ - if (SDL_GetMouseFocus() == window_data->sdlwindow && seat->keyboard.focus != window_data && - !window_data->pointer_focus_count && !Wayland_SurfaceHasActiveTouches(seat->display, surface)) { + if (SDL_GetMouseFocus() == window_data->sdlwindow && !window_data->keyboard_focus_count && + !window_data->pointer_focus_count && !window_data->active_touch_count) { SDL_SetMouseFocus(NULL); } } @@ -1308,39 +1322,11 @@ static void touch_handler_frame(void *data, struct wl_touch *touch) static void touch_handler_cancel(void *data, struct wl_touch *touch) { SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data; - struct SDL_WaylandTouchPoint *tp, *temp; + SDL_WaylandTouchPoint *tp, *temp; + // Need the safe loop variant here as cancelling a touch point removes it from the list. wl_list_for_each_safe (tp, temp, &seat->touch.points, link) { - bool removed = false; - - if (tp->surface) { - SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(tp->surface); - - if (window_data) { - const float x = (float)(wl_fixed_to_double(tp->fx) / window_data->current.logical_width); - const float y = (float)(wl_fixed_to_double(tp->fy) / window_data->current.logical_height); - - SDL_SendTouch(0, (SDL_TouchID)(uintptr_t)touch, - (SDL_FingerID)(tp->id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_CANCELED, x, y, 0.0f); - - // Remove the touch from the list before checking for still-active touches on the surface. - WAYLAND_wl_list_remove(&tp->link); - removed = true; - - /* If the window currently has mouse focus, the keyboard focus is another window or NULL, the window has no - * pointers active on it, and the surface has no active touch events, then consider mouse focus to be lost. - */ - if (SDL_GetMouseFocus() == window_data->sdlwindow && seat->keyboard.focus != window_data && - !window_data->pointer_focus_count && !Wayland_SurfaceHasActiveTouches(seat->display, tp->surface)) { - SDL_SetMouseFocus(NULL); - } - } - } - - if (!removed) { - WAYLAND_wl_list_remove(&tp->link); - } - SDL_free(tp); + Wayland_SeatCancelTouch(seat, tp); } } @@ -1924,8 +1910,7 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, /* If the window has mouse focus, has no pointers within it, and no active touches, consider * mouse focus to be lost. */ - if (SDL_GetMouseFocus() == window->sdlwindow && !window->pointer_focus_count && - !Wayland_SurfaceHasActiveTouches(seat->display, surface)) { + if (SDL_GetMouseFocus() == window->sdlwindow && !window->pointer_focus_count && !window->active_touch_count) { SDL_SetMouseFocus(NULL); } } @@ -3416,6 +3401,36 @@ void Wayland_DisplayCreateSeat(SDL_VideoData *display, struct wl_seat *wl_seat, WAYLAND_wl_display_flush(display->display); } +void Wayland_DisplayRemoveWindowReferencesFromSeats(SDL_VideoData *display, SDL_WindowData *window) +{ + SDL_WaylandSeat *seat; + wl_list_for_each (seat, &display->seat_list, link) + { + if (seat->keyboard.focus == window) { + keyboard_handle_leave(seat, seat->keyboard.wl_keyboard, 0, window->surface); + } + + if (seat->pointer.focus == window) { + pointer_handle_leave(seat, seat->pointer.wl_pointer, 0, window->surface); + } + + // Need the safe loop variant here as cancelling a touch point removes it from the list. + SDL_WaylandTouchPoint *tp, *temp; + wl_list_for_each_safe (tp, temp, &seat->touch.points, link) { + if (tp->surface == window->surface) { + Wayland_SeatCancelTouch(seat, tp); + } + } + + SDL_WaylandPenTool *tool; + wl_list_for_each (tool, &seat->tablet.tool_list, link) { + if (tool->tool_focus == window->sdlwindow) { + tablet_tool_handle_proximity_out(tool, tool->wltool); + } + } + } +} + void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events) { if (!seat) { diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index 3c3aed69d5..a6c1eed589 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -205,6 +205,7 @@ extern bool Wayland_SeatHasRelativePointerFocus(SDL_WaylandSeat *seat); extern void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat); extern void Wayland_DisplayUpdatePointerGrabs(SDL_VideoData *display, SDL_WindowData *window); extern void Wayland_DisplayUpdateKeyboardGrabs(SDL_VideoData *display, SDL_WindowData *window); +extern void Wayland_DisplayRemoveWindowReferencesFromSeats(SDL_VideoData *display, SDL_WindowData *window); /* The implicit grab serial needs to be updated on: * - Keyboard key down/up diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index b019eff2f9..7a06d37dbf 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -3093,6 +3093,12 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) WAYLAND_wl_display_roundtrip(data->display); } + /* The compositor should have relinquished keyboard, pointer, touch, and tablet tool focus when the toplevel + * window was destroyed upon being hidden, but there is no guarantee of this, so ensure that all references + * to the window held by seats are released before destroying the underlying surface and struct. + */ + Wayland_DisplayRemoveWindowReferencesFromSeats(data, wind); + #ifdef SDL_VIDEO_OPENGL_EGL if (wind->egl_surface) { SDL_EGL_DestroySurface(_this, wind->egl_surface); diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 343a0ed251..7099462abd 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -132,9 +132,10 @@ struct SDL_WindowData struct Wayland_SHMBuffer *icon_buffers; int icon_buffer_count; - // Keyboard and pointer focus refcount. + // Keyboard, pointer, and touch focus refcount. int keyboard_focus_count; int pointer_focus_count; + int active_touch_count; struct { From 70b2d162e33a6d9b9801cd939d040fdccc856653 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Tue, 13 May 2025 13:09:43 +0100 Subject: [PATCH 63/83] audio: Assert that all devices from device_hash are the appropriate type The keys and values of device_hash are pairs `(SDL_AudioDeviceID devid, void *dev)` where dev can be either a `SDL_AudioDevice *` or a `SDL_LogicalAudioDevice *`, depending on bit 1 of devid. We can confirm that we have got this right by looking at the instance_id member, because logical audio devices happen to start with the devid, whereas physical devices start with a pointer which is unlikely to match the devid by chance. Signed-off-by: Simon McVittie --- src/audio/SDL_audio.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 22f8cd943c..9dff4ca999 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -410,6 +410,7 @@ static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid, SDL_LockRWLockForReading(current_audio.device_hash_lock); SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &logdev); if (logdev) { + SDL_assert(logdev->instance_id == devid); device = logdev->physical_device; SDL_assert(device != NULL); RefPhysicalAudioDevice(device); // reference it, in case the logical device migrates to a new default. @@ -459,6 +460,7 @@ static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) // ! } else { SDL_LockRWLockForReading(current_audio.device_hash_lock); SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &device); + SDL_assert(device->instance_id == devid); SDL_UnlockRWLock(current_audio.device_hash_lock); if (!device) { @@ -883,6 +885,7 @@ static bool SDLCALL FindLowestDeviceID(void *userdata, const SDL_HashTable *tabl if (isphysical && (devid_recording == data->recording) && (devid < data->highest)) { data->highest = devid; data->result = (SDL_AudioDevice *) value; + SDL_assert(data->result->instance_id == devid); } return true; // keep iterating. } @@ -1051,7 +1054,10 @@ static bool SDLCALL DestroyOnePhysicalAudioDevice(void *userdata, const SDL_Hash const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; const bool isphysical = !!(devid & (1<<1)); if (isphysical) { - DestroyPhysicalAudioDevice((SDL_AudioDevice *) value); + SDL_AudioDevice *dev = (SDL_AudioDevice *) value; + + SDL_assert(dev->instance_id == devid); + DestroyPhysicalAudioDevice(dev); } return true; // keep iterating. } @@ -1485,6 +1491,7 @@ static bool SDLCALL FindAudioDeviceByCallback(void *userdata, const SDL_HashTabl SDL_AudioDevice *device = (SDL_AudioDevice *) value; if (data->callback(device, data->userdata)) { // found it? data->retval = device; + SDL_assert(data->retval->instance_id == devid); return false; // stop iterating, we found it. } } @@ -1541,9 +1548,11 @@ const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) SDL_SetError("Invalid audio device instance ID"); } else if (islogical) { const SDL_LogicalAudioDevice *logdev = (const SDL_LogicalAudioDevice *) vdev; + SDL_assert(logdev->instance_id == devid); result = SDL_GetPersistentString(logdev->physical_device->name); } else { const SDL_AudioDevice *device = (const SDL_AudioDevice *) vdev; + SDL_assert(device->instance_id == devid); result = SDL_GetPersistentString(device->name); } SDL_UnlockRWLock(current_audio.device_hash_lock); From cd95152b2cfeaffe74a20be3490b625f5cb798b1 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 13 May 2025 09:07:15 -0700 Subject: [PATCH 64/83] Fixed crash if out of memory in the Vulkan GPU driver --- src/gpu/vulkan/SDL_gpu_vulkan.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 8fc3af87ce..449ee33328 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -4785,7 +4785,7 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( windowData->inFlightFences[i] = NULL; } - + windowData->renderFinishedSemaphore = SDL_malloc( sizeof(VkSemaphore) * windowData->imageCount); for (i = 0; i < windowData->imageCount; i += 1) { @@ -11610,7 +11610,7 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) { // Set up dummy VulkanRenderer VulkanRenderer *renderer; - Uint8 result; + bool result = false; if (_this->Vulkan_CreateSurface == NULL) { return false; @@ -11620,16 +11620,16 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) return false; } - renderer = (VulkanRenderer *)SDL_malloc(sizeof(VulkanRenderer)); - SDL_memset(renderer, '\0', sizeof(VulkanRenderer)); - - result = VULKAN_INTERNAL_PrepareVulkan(renderer); - - if (result) { - renderer->vkDestroyInstance(renderer->instance, NULL); + renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); + if (renderer) { + result = VULKAN_INTERNAL_PrepareVulkan(renderer); + if (result) { + renderer->vkDestroyInstance(renderer->instance, NULL); + } + SDL_free(renderer); } - SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); + return result; } @@ -11650,8 +11650,12 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S return NULL; } - renderer = (VulkanRenderer *)SDL_malloc(sizeof(VulkanRenderer)); - SDL_memset(renderer, '\0', sizeof(VulkanRenderer)); + renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); + if (!renderer) { + SDL_Vulkan_UnloadLibrary(); + return false; + } + renderer->debugMode = debugMode; renderer->preferLowPower = preferLowPower; renderer->allowedFramesInFlight = 2; From 3304d24bea47d0de9a7f841fde517695ad4d6e6f Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Mon, 12 May 2025 19:16:23 -0400 Subject: [PATCH 65/83] Revert "x11: Filter out duplicate key presses when an IME is active" This reverts commit f4813ca2cf9953f60ec406ab63e07bd68f56c4ee. --- src/video/x11/SDL_x11events.c | 8 ++------ src/video/x11/SDL_x11video.h | 2 -- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 364e05a779..a4e5d9caf6 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -988,11 +988,8 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_ } if (pressed) { - // Duplicate events may be sent when an IME is active; don't send multiple keydown events for the same serial. - if (videodata->last_key_down_serial != xevent->xkey.serial) { - X11_HandleModifierKeys(videodata, scancode, true, true); - SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true); - } + X11_HandleModifierKeys(videodata, scancode, true, true); + SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true); // Synthesize a text event if the IME didn't consume a printable character if (*text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) { @@ -1002,7 +999,6 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_ } X11_UpdateUserTime(windowdata, xevent->xkey.time); - videodata->last_key_down_serial = xevent->xkey.serial; } else { if (X11_KeyRepeat(display, xevent)) { // We're about to get a repeated key down, ignore the key up diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h index d5a9299b4e..a336a800f5 100644 --- a/src/video/x11/SDL_x11video.h +++ b/src/video/x11/SDL_x11video.h @@ -140,8 +140,6 @@ struct SDL_VideoData int xinput_master_pointer_device; bool xinput_hierarchy_changed; - unsigned long last_key_down_serial; - int xrandr_event_base; struct { From 1eeffc5933d49ee3b9de032a205da530ee83c786 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Mon, 12 May 2025 19:16:31 -0400 Subject: [PATCH 66/83] Revert "x11: Send key events for dead keys consumed by the IME" This and its related commits introduced some bugs and quirks such as duplicated and delayed/missed key events that can't be easily worked around, so revert this for now. This reverts commit 47162a4168c4607e8771bab020ea8f2acd16121a. --- src/video/x11/SDL_x11events.c | 39 +++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index a4e5d9caf6..84e466255d 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -987,26 +987,29 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_ } } + if (!handled_by_ime) { + if (pressed) { + X11_HandleModifierKeys(videodata, scancode, true, true); + SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true); + + if (*text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) { + text[text_length] = '\0'; + X11_ClearComposition(windowdata); + SDL_SendKeyboardText(text); + } + } else { + if (X11_KeyRepeat(display, xevent)) { + // We're about to get a repeated key down, ignore the key up + return; + } + + X11_HandleModifierKeys(videodata, scancode, false, true); + SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, false); + } + } + if (pressed) { - X11_HandleModifierKeys(videodata, scancode, true, true); - SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true); - - // Synthesize a text event if the IME didn't consume a printable character - if (*text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) { - text[text_length] = '\0'; - X11_ClearComposition(windowdata); - SDL_SendKeyboardText(text); - } - X11_UpdateUserTime(windowdata, xevent->xkey.time); - } else { - if (X11_KeyRepeat(display, xevent)) { - // We're about to get a repeated key down, ignore the key up - return; - } - - X11_HandleModifierKeys(videodata, scancode, false, true); - SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, false); } } From f85f83ec7c1c22755c14c2b0a5953620bf8cd833 Mon Sep 17 00:00:00 2001 From: Manuel <32620529+ManuBlack@users.noreply.github.com> Date: Wed, 14 May 2025 03:28:28 +0300 Subject: [PATCH 67/83] SDL GPU: Implemented opt out Vulkan device features (#13016) --- include/SDL3/SDL_gpu.h | 42 ++++++++++++++++++------- src/gpu/SDL_gpu.c | 4 +-- src/gpu/SDL_sysgpu.h | 2 +- src/gpu/d3d12/SDL_gpu_d3d12.c | 2 +- src/gpu/metal/SDL_gpu_metal.m | 2 +- src/gpu/vulkan/SDL_gpu_vulkan.c | 55 +++++++++++++++++++++------------ 6 files changed, 71 insertions(+), 36 deletions(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index def7cc7728..c627b268e0 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2254,6 +2254,22 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * - `SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING`: the prefix to * use for all vertex semantics, default is "TEXCOORD". * + * With the Vulkan renderer: + * + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOL`: Enable device feature + * shaderClipDistance. If disabled, clip distances are not supported in shader code: + * gl_ClipDistance[] built-ins of GLSL, SV_ClipDistance0/1 semantics of HLSL and + * [[clip_distance]] attribute of Metal. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOL`: Enable device feature + * depthClamp. If disabled, there is no depth clamp support and enable_depth_clip in + * SDL_GPURasterizerState must always be set to true. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOL`: Enable device feature + * drawIndirectFirstInstance. If disabled, the argument first_instance of + * SDL_GPUIndirectDrawCommand must be set to zero. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOL`: Enable device feature + * samplerAnisotropy. If disabled, enable_anisotropy of SDL_GPUSamplerCreateInfo must + * be set to false. Defaults to true. + * * \param props the properties to use. * \returns a GPU context on success or NULL on failure; call SDL_GetError() * for more information. @@ -2268,17 +2284,21 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDeviceWithProperties( SDL_PropertiesID props); -#define SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN "SDL.gpu.device.create.debugmode" -#define SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN "SDL.gpu.device.create.preferlowpower" -#define SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN "SDL.gpu.device.create.verbose" -#define SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING "SDL.gpu.device.create.name" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN "SDL.gpu.device.create.shaders.private" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN "SDL.gpu.device.create.shaders.spirv" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN "SDL.gpu.device.create.shaders.dxbc" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN "SDL.gpu.device.create.shaders.dxil" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN "SDL.gpu.device.create.shaders.msl" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN "SDL.gpu.device.create.shaders.metallib" -#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING "SDL.gpu.device.create.d3d12.semantic" +#define SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN "SDL.gpu.device.create.debugmode" +#define SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN "SDL.gpu.device.create.preferlowpower" +#define SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN "SDL.gpu.device.create.verbose" +#define SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING "SDL.gpu.device.create.name" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN "SDL.gpu.device.create.shaders.private" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN "SDL.gpu.device.create.shaders.spirv" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN "SDL.gpu.device.create.shaders.dxbc" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN "SDL.gpu.device.create.shaders.dxil" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN "SDL.gpu.device.create.shaders.msl" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN "SDL.gpu.device.create.shaders.metallib" +#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING "SDL.gpu.device.create.d3d12.semantic" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOLEAN "SDL.gpu.device.create.vulkan.shaderclipdistance" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOLEAN "SDL.gpu.device.create.vulkan.depthclamp" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN "SDL.gpu.device.create.vulkan.drawindirectfirstinstance" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN "SDL.gpu.device.create.vulkan.sampleranisotropy" /** * Destroys a GPU context previously returned by SDL_CreateGPUDevice. diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c index 54a66e6b0d..ea86aabcaa 100644 --- a/src/gpu/SDL_gpu.c +++ b/src/gpu/SDL_gpu.c @@ -450,7 +450,7 @@ static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props) SDL_SetError("Required shader format for backend %s not provided!", gpudriver); return NULL; } - if (backends[i]->PrepareDriver(_this)) { + if (backends[i]->PrepareDriver(_this, props)) { return backends[i]; } } @@ -465,7 +465,7 @@ static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props) // Don't select a backend which doesn't support the app's shaders. continue; } - if (backends[i]->PrepareDriver(_this)) { + if (backends[i]->PrepareDriver(_this, props)) { return backends[i]; } } diff --git a/src/gpu/SDL_sysgpu.h b/src/gpu/SDL_sysgpu.h index ab69d64210..d4f532fde6 100644 --- a/src/gpu/SDL_sysgpu.h +++ b/src/gpu/SDL_sysgpu.h @@ -1142,7 +1142,7 @@ typedef struct SDL_GPUBootstrap { const char *name; const SDL_GPUShaderFormat shader_formats; - bool (*PrepareDriver)(SDL_VideoDevice *_this); + bool (*PrepareDriver)(SDL_VideoDevice *_this, SDL_PropertiesID props); SDL_GPUDevice *(*CreateDevice)(bool debug_mode, bool prefer_low_power, SDL_PropertiesID props); } SDL_GPUBootstrap; diff --git a/src/gpu/d3d12/SDL_gpu_d3d12.c b/src/gpu/d3d12/SDL_gpu_d3d12.c index 8f5d85bb82..2b65775e54 100644 --- a/src/gpu/d3d12/SDL_gpu_d3d12.c +++ b/src/gpu/d3d12/SDL_gpu_d3d12.c @@ -8317,7 +8317,7 @@ static void D3D12_INTERNAL_InitBlitResources( } } -static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) +static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) { #if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) return true; diff --git a/src/gpu/metal/SDL_gpu_metal.m b/src/gpu/metal/SDL_gpu_metal.m index c8b894f3ba..9450b6a62c 100644 --- a/src/gpu/metal/SDL_gpu_metal.m +++ b/src/gpu/metal/SDL_gpu_metal.m @@ -4307,7 +4307,7 @@ static bool METAL_SupportsTextureFormat( // Device Creation -static bool METAL_PrepareDriver(SDL_VideoDevice *this) +static bool METAL_PrepareDriver(SDL_VideoDevice *this, SDL_PropertiesID props) { if (@available(macOS 10.14, iOS 13.0, tvOS 13.0, *)) { return (this->Metal_CreateView != NULL); diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 449ee33328..2f7bd82b3f 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -1088,6 +1088,7 @@ struct VulkanRenderer VkPhysicalDevice physicalDevice; VkPhysicalDeviceProperties2KHR physicalDeviceProperties; VkPhysicalDeviceDriverPropertiesKHR physicalDeviceDriverProperties; + VkPhysicalDeviceFeatures desiredDeviceFeatures; VkDevice logicalDevice; Uint8 integratedMemoryNotification; Uint8 outOfDeviceLocalMemoryWarning; @@ -11220,12 +11221,14 @@ static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( renderer->vkGetPhysicalDeviceFeatures( physicalDevice, &deviceFeatures); - if (!deviceFeatures.independentBlend || - !deviceFeatures.imageCubeArray || - !deviceFeatures.depthClamp || - !deviceFeatures.shaderClipDistance || - !deviceFeatures.drawIndirectFirstInstance || - !deviceFeatures.sampleRateShading) { + + if ((!deviceFeatures.independentBlend && renderer->desiredDeviceFeatures.independentBlend) || + (!deviceFeatures.imageCubeArray && renderer->desiredDeviceFeatures.imageCubeArray) || + (!deviceFeatures.depthClamp && renderer->desiredDeviceFeatures.depthClamp) || + (!deviceFeatures.shaderClipDistance && renderer->desiredDeviceFeatures.shaderClipDistance) || + (!deviceFeatures.drawIndirectFirstInstance && renderer->desiredDeviceFeatures.drawIndirectFirstInstance) || + (!deviceFeatures.sampleRateShading && renderer->desiredDeviceFeatures.sampleRateShading) || + (!deviceFeatures.samplerAnisotropy && renderer->desiredDeviceFeatures.samplerAnisotropy)) { return 0; } @@ -11441,7 +11444,6 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( { VkResult vulkanResult; VkDeviceCreateInfo deviceCreateInfo; - VkPhysicalDeviceFeatures desiredDeviceFeatures; VkPhysicalDeviceFeatures haveDeviceFeatures; VkPhysicalDevicePortabilitySubsetFeaturesKHR portabilityFeatures; const char **deviceExtensions; @@ -11465,22 +11467,13 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( // specifying used device features - SDL_zero(desiredDeviceFeatures); - desiredDeviceFeatures.independentBlend = VK_TRUE; - desiredDeviceFeatures.samplerAnisotropy = VK_TRUE; - desiredDeviceFeatures.imageCubeArray = VK_TRUE; - desiredDeviceFeatures.depthClamp = VK_TRUE; - desiredDeviceFeatures.shaderClipDistance = VK_TRUE; - desiredDeviceFeatures.drawIndirectFirstInstance = VK_TRUE; - desiredDeviceFeatures.sampleRateShading = VK_TRUE; - if (haveDeviceFeatures.fillModeNonSolid) { - desiredDeviceFeatures.fillModeNonSolid = VK_TRUE; + renderer->desiredDeviceFeatures.fillModeNonSolid = VK_TRUE; renderer->supportsFillModeNonSolid = true; } if (haveDeviceFeatures.multiDrawIndirect) { - desiredDeviceFeatures.multiDrawIndirect = VK_TRUE; + renderer->desiredDeviceFeatures.multiDrawIndirect = VK_TRUE; renderer->supportsMultiDrawIndirect = true; } @@ -11521,7 +11514,7 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( deviceCreateInfo.enabledExtensionCount); CreateDeviceExtensionArray(&renderer->supports, deviceExtensions); deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions; - deviceCreateInfo.pEnabledFeatures = &desiredDeviceFeatures; + deviceCreateInfo.pEnabledFeatures = &renderer->desiredDeviceFeatures; vulkanResult = renderer->vkCreateDevice( renderer->physicalDevice, @@ -11606,7 +11599,7 @@ static bool VULKAN_INTERNAL_PrepareVulkan( return true; } -static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) +static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) { // Set up dummy VulkanRenderer VulkanRenderer *renderer; @@ -11622,6 +11615,17 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); if (renderer) { + // Opt out device features (higher compatibility in exchange for reduced functionality) + renderer->desiredDeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + + // These features have near universal support so they are always enabled + renderer->desiredDeviceFeatures.independentBlend = VK_TRUE; + renderer->desiredDeviceFeatures.sampleRateShading = VK_TRUE; + renderer->desiredDeviceFeatures.imageCubeArray = VK_TRUE; + result = VULKAN_INTERNAL_PrepareVulkan(renderer); if (result) { renderer->vkDestroyInstance(renderer->instance, NULL); @@ -11660,6 +11664,17 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->preferLowPower = preferLowPower; renderer->allowedFramesInFlight = 2; + // Opt out device features (higher compatibility in exchange for reduced functionality) + renderer->desiredDeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + + // These features have near universal support so they are always enabled + renderer->desiredDeviceFeatures.independentBlend = VK_TRUE; + renderer->desiredDeviceFeatures.sampleRateShading = VK_TRUE; + renderer->desiredDeviceFeatures.imageCubeArray = VK_TRUE; + if (!VULKAN_INTERNAL_PrepareVulkan(renderer)) { SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); From 83cbf7f81183a21b2f3b11cf9e92db4b052e99c5 Mon Sep 17 00:00:00 2001 From: SDL Wiki Bot Date: Wed, 14 May 2025 00:29:20 +0000 Subject: [PATCH 68/83] Sync SDL3 wiki -> header [ci skip] --- include/SDL3/SDL_gpu.h | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index c627b268e0..a99822aba2 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2256,19 +2256,22 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * * With the Vulkan renderer: * - * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOL`: Enable device feature - * shaderClipDistance. If disabled, clip distances are not supported in shader code: - * gl_ClipDistance[] built-ins of GLSL, SV_ClipDistance0/1 semantics of HLSL and - * [[clip_distance]] attribute of Metal. Defaults to true. - * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOL`: Enable device feature - * depthClamp. If disabled, there is no depth clamp support and enable_depth_clip in - * SDL_GPURasterizerState must always be set to true. Defaults to true. - * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOL`: Enable device feature - * drawIndirectFirstInstance. If disabled, the argument first_instance of - * SDL_GPUIndirectDrawCommand must be set to zero. Defaults to true. - * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOL`: Enable device feature - * samplerAnisotropy. If disabled, enable_anisotropy of SDL_GPUSamplerCreateInfo must - * be set to false. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOL`: Enable + * device feature shaderClipDistance. If disabled, clip distances are not + * supported in shader code: gl_ClipDistance[] built-ins of GLSL, + * SV_ClipDistance0/1 semantics of HLSL and [[clip_distance]] attribute of + * Metal. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOL`: Enable device + * feature depthClamp. If disabled, there is no depth clamp support and + * enable_depth_clip in SDL_GPURasterizerState must always be set to true. + * Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOL`: Enable device + * feature drawIndirectFirstInstance. If disabled, the argument + * first_instance of SDL_GPUIndirectDrawCommand must be set to zero. + * Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOL`: Enable device + * feature samplerAnisotropy. If disabled, enable_anisotropy of + * SDL_GPUSamplerCreateInfo must be set to false. Defaults to true. * * \param props the properties to use. * \returns a GPU context on success or NULL on failure; call SDL_GetError() From cf62637261138f882d5bee33094e0e81558d04d7 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 12 May 2025 16:35:47 -0700 Subject: [PATCH 69/83] joystick: Add support for Xbox One Chatpad attachment --- src/joystick/hidapi/SDL_hidapi_gip.c | 137 +++++++++++++++++++++++++-- 1 file changed, 131 insertions(+), 6 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c index f17ec628fb..efdfc3a05b 100644 --- a/src/joystick/hidapi/SDL_hidapi_gip.c +++ b/src/joystick/hidapi/SDL_hidapi_gip.c @@ -22,6 +22,7 @@ #ifdef SDL_JOYSTICK_HIDAPI +#include "../../events/SDL_keyboard_c.h" #include "../SDL_sysjoystick.h" #include "SDL_hidapijoystick_c.h" #include "SDL_hidapi_rumble.h" @@ -427,6 +428,7 @@ typedef struct GIP_Attachment struct GIP_Device *device; Uint8 attachment_index; SDL_JoystickID joystick; + SDL_KeyboardID keyboard; Uint8 fragment_message; Uint16 total_length; @@ -461,6 +463,12 @@ typedef struct GIP_Attachment Uint8 last_input[64]; + Uint8 last_modifiers; + bool capslock; + SDL_Keycode last_key; + Uint32 altcode; + int altcode_digit; + GIP_AttachmentType attachment_type; GIP_PaddleFormat paddle_format; Uint32 features; @@ -769,6 +777,11 @@ static bool GIP_SendVendorMessage( NULL); } +static bool GIP_AttachmentIsController(GIP_Attachment *attachment) +{ + return attachment->attachment_type != GIP_TYPE_CHATPAD; +} + static void GIP_MetadataFree(GIP_Metadata *metadata) { if (metadata->device.audio_formats) { @@ -933,6 +946,9 @@ static bool GIP_ParseDeviceMetadata(GIP_Metadata *metadata, const Uint8 *bytes, } device->hid_descriptor = SDL_malloc(device->hid_descriptor_size); SDL_memcpy(device->hid_descriptor, &bytes[buffer_offset + 1], device->hid_descriptor_size); +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("GIP received HID descriptor: size = %d", device->hid_descriptor, device->hid_descriptor_size); +#endif } } @@ -1197,9 +1213,13 @@ static bool GIP_SendInitSequence(GIP_Attachment *attachment) GIP_SendVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, 0, NULL, 0); } - if (!attachment->joystick) { + if ((!attachment->attachment_index || GIP_AttachmentIsController(attachment)) && !attachment->joystick) { return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick); } + if (attachment->attachment_type == GIP_TYPE_CHATPAD && !attachment->keyboard) { + attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment; + SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad", true); + } return true; } @@ -1424,7 +1444,7 @@ static bool GIP_HandleCommandMetadataRespose( { GIP_Metadata metadata = {0}; const GUID *expected_guid = NULL; - bool found_expected_guid = false; + bool found_expected_guid; bool found_controller_guid = false; int i; @@ -1494,6 +1514,7 @@ static bool GIP_HandleCommandMetadataRespose( } } + found_expected_guid = !expected_guid; for (i = 0; i < metadata.device.num_supported_interfaces; i++) { const GUID* guid = &metadata.device.supported_interfaces[i]; #ifdef DEBUG_XBOX_PROTOCOL @@ -1539,7 +1560,7 @@ static bool GIP_HandleCommandMetadataRespose( } } - if (!found_expected_guid || !found_controller_guid) { + if (!found_expected_guid || (GIP_AttachmentIsController(attachment) && !found_controller_guid)) { SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Controller was missing expected GUID. This controller probably won't work on an actual Xbox."); } @@ -1701,9 +1722,95 @@ static bool GIP_HandleCommandHidReport( const Uint8 *bytes, int num_bytes) { - // TODO - SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented HID Report message"); - return false; + Uint64 timestamp = SDL_GetTicksNS(); + // SDL doesn't have HID descriptor parsing, so we have to hardcode for the Chatpad descriptor instead. + // I don't know of any other devices that emit HID reports, so this should be safe. + if (attachment->attachment_type != GIP_TYPE_CHATPAD || !attachment->keyboard || num_bytes != 8) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented HID Report message"); + return false; + } + + Uint8 modifiers = bytes[0]; + Uint8 changed_modifiers = modifiers ^ attachment->last_modifiers; + if (changed_modifiers & 0x02) { + if (modifiers & 0x02) { + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_LSHIFT, true); + } else { + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_LSHIFT, false); + } + } + // The chatpad has several non-ASCII characters that it sends as Alt codes + if (changed_modifiers & 0x04) { + if (modifiers & 0x04) { + attachment->altcode_digit = 0; + attachment->altcode = 0; + } else { + if (attachment->altcode_digit == 4) { + char utf8[4] = {0}; + // Some Alt codes don't match their Unicode codepoint for some reason + switch (attachment->altcode) { + case 128: + SDL_UCS4ToUTF8(0x20AC, utf8); + break; + case 138: + SDL_UCS4ToUTF8(0x0160, utf8); + break; + case 140: + SDL_UCS4ToUTF8(0x0152, utf8); + break; + case 154: + SDL_UCS4ToUTF8(0x0161, utf8); + break; + case 156: + SDL_UCS4ToUTF8(0x0153, utf8); + break; + default: + SDL_UCS4ToUTF8(attachment->altcode, utf8); + break; + } + SDL_SendKeyboardText(utf8); + } + attachment->altcode_digit = -1; + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_NUMLOCKCLEAR, true); + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_NUMLOCKCLEAR, false); + } + } + + if (!bytes[2] && attachment->last_key) { + if (attachment->last_key == SDL_SCANCODE_CAPSLOCK) { + attachment->capslock = !attachment->capslock; + } + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, attachment->last_key, false); + if (!(attachment->last_modifiers & 0xfd)) { + SDL_Keycode keycode = SDL_GetKeymapKeycode(NULL, + attachment->last_key, + ((attachment->last_modifiers & 0x02) || attachment->capslock) ? SDL_KMOD_SHIFT : 0); + if (keycode && keycode < 0x80) { + char text[2] = { (char)keycode }; + SDL_SendKeyboardText(text); + } + } + attachment->last_key = 0; + } else { + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, bytes[2], true); + attachment->last_key = bytes[2]; + + if ((modifiers & 0x04) && attachment->altcode_digit >= 0) { + int digit = bytes[2] - SDL_SCANCODE_KP_1 + 1; + if (digit < 1 || digit > 10) { + attachment->altcode_digit = -1; + } else { + attachment->altcode_digit++; + attachment->altcode *= 10; + if (digit < 10) { + attachment->altcode += digit; + } + } + } + } + + attachment->last_modifiers = modifiers; + return true; } static bool GIP_HandleCommandExtended( @@ -2085,6 +2192,18 @@ static bool GIP_HandleSystemMessage( const Uint8 *bytes, int num_bytes) { + if (attachment->attachment_index > 0 && attachment->attachment_type == GIP_TYPE_UNKNOWN) { + // XXX If we reattach to a controller after it's been initialized, it might have + // attachments we don't know about. Try to figure out what this one is. + if (header->message_type == GIP_CMD_HID_REPORT && num_bytes == 8) { + if (!attachment->keyboard) { + attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment; + SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad", true); + } + attachment->attachment_type = GIP_TYPE_CHATPAD; + attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_HID_REPORT); + } + } if (!GIP_SupportsSystemMessage(attachment, header->message_type, true)) { SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Received claimed-unsupported system message type %02x", @@ -2128,6 +2247,9 @@ static GIP_Attachment * GIP_EnsureAttachment(GIP_Device *device, Uint8 attachmen if (!attachment) { attachment = SDL_calloc(1, sizeof(*attachment)); attachment->attachment_index = attachment_index; + if (attachment_index > 0) { + attachment->attachment_type = GIP_TYPE_UNKNOWN; + } attachment->device = device; attachment->metadata.device.in_system_messages[0] = GIP_DEFAULT_IN_SYSTEM_MESSAGES; attachment->metadata.device.out_system_messages[0] = GIP_DEFAULT_OUT_SYSTEM_MESSAGES; @@ -2679,6 +2801,9 @@ static void HIDAPI_DriverGIP_FreeDevice(SDL_HIDAPI_Device *device) SDL_free(attachment->fragment_data); attachment->fragment_data = NULL; } + if (attachment->keyboard) { + SDL_RemoveKeyboard(attachment->keyboard, true); + } GIP_MetadataFree(&attachment->metadata); SDL_free(attachment); context->attachments[i] = NULL; From 1f7aa16eaefd0d20402791ab04eb2a707b2f597b Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 14 May 2025 11:34:42 -0400 Subject: [PATCH 70/83] wikiheaders: Man pages should escape apostrophe chars. This prevents problems if one starts a line, which would cause it to be interpreted as a command character. Fixes #13038. --- build-scripts/wikiheaders.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/build-scripts/wikiheaders.pl b/build-scripts/wikiheaders.pl index 51b953dfde..506ad22dd4 100755 --- a/build-scripts/wikiheaders.pl +++ b/build-scripts/wikiheaders.pl @@ -426,6 +426,7 @@ sub dewikify_chunk { # make sure these can't become part of roff syntax. $str =~ s/\./\\[char46]/gms; $str =~ s/"/\\(dq/gms; + $str =~ s/'/\\(aq/gms; if ($wikitype eq 'mediawiki') { # Dump obvious wikilinks. From 8aa5b97bb5be8e8f62fd76c3181439e20f3de5e9 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Wed, 14 May 2025 09:57:47 -0400 Subject: [PATCH 71/83] renderer: Always use the output size when updating the main view The main view always reflects the size of the output, so don't use the dimensions of the currently bound render target texture when updating it, or it will reflect an incorrect size when the render target texture is unbound. --- src/render/SDL_render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 342b25b2ec..cfe9fe6fa0 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -2627,7 +2627,7 @@ static void UpdateLogicalPresentation(SDL_Renderer *renderer) const float logical_h = view->logical_h; int iwidth, iheight; - if (renderer->target) { + if (!is_main_view && renderer->target) { iwidth = (int)renderer->target->w; iheight = (int)renderer->target->h; } else { From ec685e87fd3b914c6d030d91f7e375be58ff656c Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 14 May 2025 10:12:41 -0700 Subject: [PATCH 72/83] Clarify logic in UpdateLogicalPresentation() --- src/render/SDL_render.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index cfe9fe6fa0..38ee1ae3be 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -2627,11 +2627,12 @@ static void UpdateLogicalPresentation(SDL_Renderer *renderer) const float logical_h = view->logical_h; int iwidth, iheight; - if (!is_main_view && renderer->target) { + if (is_main_view) { + SDL_GetRenderOutputSize(renderer, &iwidth, &iheight); + } else { + SDL_assert(renderer->target != NULL); iwidth = (int)renderer->target->w; iheight = (int)renderer->target->h; - } else { - SDL_GetRenderOutputSize(renderer, &iwidth, &iheight); } view->logical_src_rect.x = 0.0f; From db154c8b9b9ac7e18bdd064c5080a23d2ecded64 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 14 May 2025 10:18:51 -0700 Subject: [PATCH 73/83] Added HIDAPI mapping for the 8BitDo SF30 Pro --- src/joystick/SDL_gamepad.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index 32d76df011..d5541ae649 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -780,7 +780,9 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) break; } } else if (vendor == USB_VENDOR_8BITDO && - (product == USB_PRODUCT_8BITDO_SN30_PRO || + (product == USB_PRODUCT_8BITDO_SF30_PRO || + product == USB_PRODUCT_8BITDO_SF30_PRO_BT || + product == USB_PRODUCT_8BITDO_SN30_PRO || product == USB_PRODUCT_8BITDO_SN30_PRO_BT || product == USB_PRODUCT_8BITDO_PRO_2 || product == USB_PRODUCT_8BITDO_PRO_2_BT)) { From 252129f433cf8fa5c930aee70f4b1e59a0314d14 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 14 May 2025 12:21:29 -0700 Subject: [PATCH 74/83] GPU: Debug mode layer and level index checks Resolves #13033 --- src/gpu/SDL_gpu.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c index ea86aabcaa..892f4a988f 100644 --- a/src/gpu/SDL_gpu.c +++ b/src/gpu/SDL_gpu.c @@ -1546,6 +1546,14 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( } } } + + if (color_target_infos[i].layer_or_depth_plane >= textureHeader->info.layer_count_or_depth) { + SDL_assert_release(!"Color target layer index must be less than the texture's layer count!"); + } + + if (color_target_infos[i].mip_level >= textureHeader->info.num_levels) { + SDL_assert_release(!"Color target mip level must be less than the texture's level count!"); + } } if (depth_stencil_target_info != NULL) { @@ -2082,6 +2090,14 @@ SDL_GPUComputePass *SDL_BeginGPUComputePass( SDL_assert_release(!"Texture must be created with COMPUTE_STORAGE_WRITE or COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE flag"); return NULL; } + + if (storage_texture_bindings[i].layer >= header->info.layer_count_or_depth) { + SDL_assert_release(!"Storage texture layer index must be less than the texture's layer count!"); + } + + if (storage_texture_bindings[i].mip_level >= header->info.num_levels) { + SDL_assert_release(!"Storage texture mip level must be less than the texture's level count!"); + } } // TODO: validate buffer usage? From 604c1921546209af5370de1f666b8862f4a15c27 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Wed, 14 May 2025 15:23:23 -0700 Subject: [PATCH 75/83] GPU: Always return NULL if beginning a pass fails an assert check --- src/gpu/SDL_gpu.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c index 892f4a988f..02c114d469 100644 --- a/src/gpu/SDL_gpu.c +++ b/src/gpu/SDL_gpu.c @@ -1522,37 +1522,46 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( if (color_target_infos[i].cycle && color_target_infos[i].load_op == SDL_GPU_LOADOP_LOAD) { SDL_assert_release(!"Cannot cycle color target when load op is LOAD!"); + return NULL; } if (color_target_infos[i].store_op == SDL_GPU_STOREOP_RESOLVE || color_target_infos[i].store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { if (color_target_infos[i].resolve_texture == NULL) { SDL_assert_release(!"Store op is RESOLVE or RESOLVE_AND_STORE but resolve_texture is NULL!"); + return NULL; } else { TextureCommonHeader *resolveTextureHeader = (TextureCommonHeader *)color_target_infos[i].resolve_texture; if (textureHeader->info.sample_count == SDL_GPU_SAMPLECOUNT_1) { SDL_assert_release(!"Store op is RESOLVE or RESOLVE_AND_STORE but texture is not multisample!"); + return NULL; } if (resolveTextureHeader->info.sample_count != SDL_GPU_SAMPLECOUNT_1) { SDL_assert_release(!"Resolve texture must have a sample count of 1!"); + return NULL; } if (resolveTextureHeader->info.format != textureHeader->info.format) { SDL_assert_release(!"Resolve texture must have the same format as its corresponding color target!"); + return NULL; } if (resolveTextureHeader->info.type == SDL_GPU_TEXTURETYPE_3D) { SDL_assert_release(!"Resolve texture must not be of TEXTURETYPE_3D!"); + return NULL; } if (!(resolveTextureHeader->info.usage & SDL_GPU_TEXTUREUSAGE_COLOR_TARGET)) { SDL_assert_release(!"Resolve texture usage must include COLOR_TARGET!"); + return NULL; } } } if (color_target_infos[i].layer_or_depth_plane >= textureHeader->info.layer_count_or_depth) { SDL_assert_release(!"Color target layer index must be less than the texture's layer count!"); + return NULL; } if (color_target_infos[i].mip_level >= textureHeader->info.num_levels) { SDL_assert_release(!"Color target mip level must be less than the texture's level count!"); + return NULL; } } @@ -1561,10 +1570,12 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( TextureCommonHeader *textureHeader = (TextureCommonHeader *)depth_stencil_target_info->texture; if (!(textureHeader->info.usage & SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) { SDL_assert_release(!"Depth target must have been created with the DEPTH_STENCIL_TARGET usage flag!"); + return NULL; } if (depth_stencil_target_info->cycle && (depth_stencil_target_info->load_op == SDL_GPU_LOADOP_LOAD || depth_stencil_target_info->stencil_load_op == SDL_GPU_LOADOP_LOAD)) { SDL_assert_release(!"Cannot cycle depth target when load op or stencil load op is LOAD!"); + return NULL; } if (depth_stencil_target_info->store_op == SDL_GPU_STOREOP_RESOLVE || @@ -1572,6 +1583,7 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( depth_stencil_target_info->store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE || depth_stencil_target_info->stencil_store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { SDL_assert_release(!"RESOLVE store ops are not supported for depth-stencil targets!"); + return NULL; } } } @@ -2093,10 +2105,12 @@ SDL_GPUComputePass *SDL_BeginGPUComputePass( if (storage_texture_bindings[i].layer >= header->info.layer_count_or_depth) { SDL_assert_release(!"Storage texture layer index must be less than the texture's layer count!"); + return NULL; } if (storage_texture_bindings[i].mip_level >= header->info.num_levels) { SDL_assert_release(!"Storage texture mip level must be less than the texture's level count!"); + return NULL; } } From b08d79b83299302b06bd4d067549c62806ddfb25 Mon Sep 17 00:00:00 2001 From: Evan Hemsley <2342303+thatcosmonaut@users.noreply.github.com> Date: Wed, 14 May 2025 16:24:05 -0700 Subject: [PATCH 76/83] GPU: Check that a texture format is valid for compute writes (#13044) --- src/gpu/SDL_gpu.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c index 02c114d469..f6e293e3cb 100644 --- a/src/gpu/SDL_gpu.c +++ b/src/gpu/SDL_gpu.c @@ -186,6 +186,114 @@ #define COPYPASS_DEVICE \ ((CommandBufferCommonHeader *)COPYPASS_COMMAND_BUFFER)->device +static bool TextureFormatIsComputeWritable[] = { + false, // INVALID + false, // A8_UNORM + true, // R8_UNORM + true, // R8G8_UNORM + true, // R8G8B8A8_UNORM + true, // R16_UNORM + true, // R16G16_UNORM + true, // R16G16B16A16_UNORM + false, // R10G10B10A2_UNORM + false, // B5G6R5_UNORM + false, // B5G5R5A1_UNORM + false, // B4G4R4A4_UNORM + false, // B8G8R8A8_UNORM + false, // BC1_UNORM + false, // BC2_UNORM + false, // BC3_UNORM + false, // BC4_UNORM + false, // BC5_UNORM + false, // BC7_UNORM + false, // BC6H_FLOAT + false, // BC6H_UFLOAT + true, // R8_SNORM + true, // R8G8_SNORM + true, // R8G8B8A8_SNORM + true, // R16_SNORM + true, // R16G16_SNORM + true, // R16G16B16A16_SNORM + true, // R16_FLOAT + true, // R16G16_FLOAT + true, // R16G16B16A16_FLOAT + true, // R32_FLOAT + true, // R32G32_FLOAT + true, // R32G32B32A32_FLOAT + false, // R11G11B10_UFLOAT + true, // R8_UINT + true, // R8G8_UINT + true, // R8G8B8A8_UINT + true, // R16_UINT + true, // R16G16_UINT + true, // R16G16B16A16_UINT + true, // R32_UINT + true, // R32G32_UINT + true, // R32G32B32A32_UINT + true, // R8_INT + true, // R8G8_INT + true, // R8G8B8A8_INT + true, // R16_INT + true, // R16G16_INT + true, // R16G16B16A16_INT + true, // R32_INT + true, // R32G32_INT + true, // R32G32B32A32_INT + false, // R8G8B8A8_UNORM_SRGB + false, // B8G8R8A8_UNORM_SRGB + false, // BC1_UNORM_SRGB + false, // BC3_UNORM_SRGB + false, // BC3_UNORM_SRGB + false, // BC7_UNORM_SRGB + false, // D16_UNORM + false, // D24_UNORM + false, // D32_FLOAT + false, // D24_UNORM_S8_UINT + false, // D32_FLOAT_S8_UINT + false, // ASTC_4x4_UNORM + false, // ASTC_5x4_UNORM + false, // ASTC_5x5_UNORM + false, // ASTC_6x5_UNORM + false, // ASTC_6x6_UNORM + false, // ASTC_8x5_UNORM + false, // ASTC_8x6_UNORM + false, // ASTC_8x8_UNORM + false, // ASTC_10x5_UNORM + false, // ASTC_10x6_UNORM + false, // ASTC_10x8_UNORM + false, // ASTC_10x10_UNORM + false, // ASTC_12x10_UNORM + false, // ASTC_12x12_UNORM + false, // ASTC_4x4_UNORM_SRGB + false, // ASTC_5x4_UNORM_SRGB + false, // ASTC_5x5_UNORM_SRGB + false, // ASTC_6x5_UNORM_SRGB + false, // ASTC_6x6_UNORM_SRGB + false, // ASTC_8x5_UNORM_SRGB + false, // ASTC_8x6_UNORM_SRGB + false, // ASTC_8x8_UNORM_SRGB + false, // ASTC_10x5_UNORM_SRGB + false, // ASTC_10x6_UNORM_SRGB + false, // ASTC_10x8_UNORM_SRGB + false, // ASTC_10x10_UNORM_SRGB + false, // ASTC_12x10_UNORM_SRGB + false, // ASTC_12x12_UNORM_SRGB + false, // ASTC_4x4_FLOAT + false, // ASTC_5x4_FLOAT + false, // ASTC_5x5_FLOAT + false, // ASTC_6x5_FLOAT + false, // ASTC_6x6_FLOAT + false, // ASTC_8x5_FLOAT + false, // ASTC_8x6_FLOAT + false, // ASTC_8x8_FLOAT + false, // ASTC_10x5_FLOAT + false, // ASTC_10x6_FLOAT + false, // ASTC_10x8_FLOAT + false, // ASTC_10x10_FLOAT + false, // ASTC_12x10_FLOAT + false // ASTC_12x12_FLOAT +}; + // Drivers #ifndef SDL_GPU_DISABLED @@ -760,6 +868,13 @@ bool SDL_GPUTextureSupportsFormat( CHECK_TEXTUREFORMAT_ENUM_INVALID(format, false) } + if ((usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE) || + (usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE)) { + if (!TextureFormatIsComputeWritable[format]) { + return false; + } + } + return device->SupportsTextureFormat( device->driverData, format, From 945eb6dc871177f454618987e1127e449fc1191d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20GINIER?= Date: Wed, 14 May 2025 06:58:28 +0200 Subject: [PATCH 77/83] MacOS: fix cocoa clipboard text Adjust Cocoa_SetClipboardData so that SetClipboardText text can be pasted outside SDL --- src/video/cocoa/SDL_cocoaclipboard.m | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/video/cocoa/SDL_cocoaclipboard.m b/src/video/cocoa/SDL_cocoaclipboard.m index 42c2ad64ba..7039ff6ad5 100644 --- a/src/video/cocoa/SDL_cocoaclipboard.m +++ b/src/video/cocoa/SDL_cocoaclipboard.m @@ -158,6 +158,17 @@ bool Cocoa_SetClipboardData(SDL_VideoDevice *_this) @autoreleasepool { SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->internal; NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + + // SetClipboardText specialization so text is available after the app quits + if (_this->clipboard_callback && _this->num_clipboard_mime_types == 1) { + if (SDL_strncmp(_this->clipboard_mime_types[0], "text/plain;charset=utf-8", 24) == 0) { + [pasteboard declareTypes:@[ NSPasteboardTypeString ] owner:nil]; + [pasteboard setString:@((char *)_this->clipboard_userdata) forType:NSPasteboardTypeString]; + data.clipboard_count = [pasteboard changeCount]; + return true; + } + } + NSPasteboardItem *newItem = [NSPasteboardItem new]; NSMutableArray *utiTypes = [NSMutableArray new]; Cocoa_PasteboardDataProvider *provider = [[Cocoa_PasteboardDataProvider alloc] initWith: _this->clipboard_callback userData: _this->clipboard_userdata]; From d16371b923fed57e5df676a84435baf1d1061a6a Mon Sep 17 00:00:00 2001 From: ScolderCreations <69083943+ScolderCreations@users.noreply.github.com> Date: Thu, 15 May 2025 11:19:33 -0700 Subject: [PATCH 78/83] Fix reference to nonexistent "README-3ds.md" --- docs/README-platforms.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README-platforms.md b/docs/README-platforms.md index 77ae411d27..46a054a8ae 100644 --- a/docs/README-platforms.md +++ b/docs/README-platforms.md @@ -11,7 +11,7 @@ - [macOS](README-macos.md) - [NetBSD](README-bsd.md) - [Nintendo Switch](README-switch.md) -- [Nintendo 3DS](README-3ds.md) +- [Nintendo 3DS](README-n3ds.md) - [OpenBSD](README-bsd.md) - [PlayStation 2](README-ps2.md) - [PlayStation 4](README-ps4.md) From 968222e74fded22f06e2af78a4616140b1db6683 Mon Sep 17 00:00:00 2001 From: danginsburg Date: Fri, 16 May 2025 12:03:46 -0400 Subject: [PATCH 79/83] Fix #13057 - fixes bug with NSEventTypeMouseMoved having a NULL window causing us to suppress future mouse move events because the window was considered out of focus. --- src/video/cocoa/SDL_cocoamouse.m | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/video/cocoa/SDL_cocoamouse.m b/src/video/cocoa/SDL_cocoamouse.m index 530ca0c874..f8f582972f 100644 --- a/src/video/cocoa/SDL_cocoamouse.m +++ b/src/video/cocoa/SDL_cocoamouse.m @@ -450,12 +450,18 @@ void Cocoa_HandleMouseEvent(SDL_VideoDevice *_this, NSEvent *event) // All events except NSEventTypeMouseExited can only happen if the window // has mouse focus, so we'll always set the focus even if we happen to miss // NSEventTypeMouseEntered, which apparently happens if the window is - // created under the mouse on macOS 12.7 + // created under the mouse on macOS 12.7. But, only set the focus if + // the event acutally has a non-NULL window, otherwise what would happen + // is that after an NSEventTypeMouseEntered there would sometimes be + // NSEventTypeMouseMoved without a window causing us to suppress subsequent + // mouse move events. NSEventType event_type = [event type]; if (event_type == NSEventTypeMouseExited) { Cocoa_MouseFocus = NULL; } else { - Cocoa_MouseFocus = [event window]; + if ([event window] != NULL) { + Cocoa_MouseFocus = [event window]; + } } switch (event_type) { From b0a282e31fcbdfaf9e899640ee8e91f620b733d6 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Wed, 7 May 2025 13:10:24 -0400 Subject: [PATCH 80/83] wayland: Enable relative pointer mode based on the window flag This can be toggled per-window, so use the individual window flags instead of the global toggle to selectively enable it only for the relevant window in a multi-seat scenario, as is already done with keyboard and pointer grabs. --- src/events/SDL_mouse.c | 5 +- src/video/wayland/SDL_waylandevents.c | 104 +++++++++++------------- src/video/wayland/SDL_waylandevents_c.h | 2 - src/video/wayland/SDL_waylandmouse.c | 12 +-- src/video/wayland/SDL_waylandvideo.c | 1 - src/video/wayland/SDL_waylandvideo.h | 2 - src/video/wayland/SDL_waylandwindow.c | 2 +- 7 files changed, 55 insertions(+), 73 deletions(-) diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c index 166518a6f8..4845bb5724 100644 --- a/src/events/SDL_mouse.c +++ b/src/events/SDL_mouse.c @@ -1314,8 +1314,9 @@ static void SDL_MaybeEnableWarpEmulation(SDL_Window *window, float x, float y) // Require two consecutive warps to the center within a certain timespan to enter warp emulation mode. const Uint64 now = SDL_GetTicksNS(); if (now - mouse->last_center_warp_time_ns < WARP_EMULATION_THRESHOLD_NS) { - if (SDL_SetRelativeMouseMode(true)) { - mouse->warp_emulation_active = true; + mouse->warp_emulation_active = true; + if (!SDL_SetRelativeMouseMode(true)) { + mouse->warp_emulation_active = false; } } diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 6aa95a5bee..ec01cbc0a2 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -912,7 +912,7 @@ static void pointer_handle_button_common(SDL_WaylandSeat *seat, uint32_t serial, * * The mouse is not captured in relative mode. */ - if (!seat->display->relative_mode_enabled || !Wayland_SeatHasRelativePointerFocus(seat)) { + if (!seat->pointer.relative_pointer) { if (seat->pointer.buttons_pressed != 0) { window->sdlwindow->flags |= SDL_WINDOW_MOUSE_CAPTURE; } else { @@ -1164,30 +1164,23 @@ static void relative_pointer_handle_relative_motion(void *data, wl_fixed_t dy_unaccel_w) { SDL_WaylandSeat *seat = data; + SDL_WindowData *window = seat->pointer.focus; + SDL_Mouse *mouse = SDL_GetMouse(); - if (seat->display->relative_mode_enabled) { - SDL_WindowData *window = seat->pointer.focus; + // Relative pointer event times are in microsecond granularity. + const Uint64 timestamp = Wayland_AdjustEventTimestampBase(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo)); - // Relative motion follows keyboard focus. - if (Wayland_SeatHasRelativePointerFocus(seat)) { - SDL_Mouse *mouse = SDL_GetMouse(); - - // Relative pointer event times are in microsecond granularity. - const Uint64 timestamp = Wayland_AdjustEventTimestampBase(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo)); - - double dx; - double dy; - if (mouse->InputTransform || !mouse->enable_relative_system_scale) { - dx = wl_fixed_to_double(dx_unaccel_w); - dy = wl_fixed_to_double(dy_unaccel_w); - } else { - dx = wl_fixed_to_double(dx_w) * window->pointer_scale.x; - dy = wl_fixed_to_double(dy_w) * window->pointer_scale.y; - } - - SDL_SendMouseMotion(timestamp, window->sdlwindow, seat->pointer.sdl_id, true, (float)dx, (float)dy); - } + double dx; + double dy; + if (mouse->InputTransform || !mouse->enable_relative_system_scale) { + dx = wl_fixed_to_double(dx_unaccel_w); + dy = wl_fixed_to_double(dy_unaccel_w); + } else { + dx = wl_fixed_to_double(dx_w) * window->pointer_scale.x; + dy = wl_fixed_to_double(dy_w) * window->pointer_scale.y; } + + SDL_SendMouseMotion(timestamp, window->sdlwindow, seat->pointer.sdl_id, true, (float)dx, (float)dy); } static const struct zwp_relative_pointer_v1_listener relative_pointer_listener = { @@ -2077,26 +2070,6 @@ static const struct wl_keyboard_listener keyboard_listener = { keyboard_handle_repeat_info, // Version 4 }; -static void Wayland_SeatCreateRelativePointer(SDL_WaylandSeat *seat) -{ - if (seat->display->relative_pointer_manager) { - if (seat->pointer.wl_pointer && !seat->pointer.relative_pointer) { - seat->pointer.relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(seat->display->relative_pointer_manager, seat->pointer.wl_pointer); - zwp_relative_pointer_v1_add_listener(seat->pointer.relative_pointer, - &relative_pointer_listener, - seat); - } - } -} - -void Wayland_DisplayInitRelativePointerManager(SDL_VideoData *display) -{ - SDL_WaylandSeat *seat; - wl_list_for_each(seat, &display->seat_list, link) { - Wayland_SeatCreateRelativePointer(seat); - } -} - static void Wayland_SeatDestroyPointer(SDL_WaylandSeat *seat, bool send_event) { // Make sure focus is removed from a surface before the pointer is destroyed. @@ -2239,8 +2212,6 @@ static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum w wl_pointer_set_user_data(seat->pointer.wl_pointer, seat); wl_pointer_add_listener(seat->pointer.wl_pointer, &pointer_listener, seat); - Wayland_SeatCreateRelativePointer(seat); - seat->pointer.sdl_id = SDL_GetNextObjectID(); if (seat->name) { @@ -3490,16 +3461,36 @@ void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events) SDL_free(seat); } -bool Wayland_SeatHasRelativePointerFocus(SDL_WaylandSeat *seat) +static void Wayland_SeatUpdateRelativePointer(SDL_WaylandSeat *seat) { - /* If a seat has both keyboard and pointer capabilities, relative focus will follow the keyboard - * attached to that seat. Otherwise, relative focus will be gained if any other seat has keyboard - * focus on the window with pointer focus. - */ - if (seat->keyboard.wl_keyboard) { - return seat->keyboard.focus && seat->keyboard.focus == seat->pointer.focus; - } else { - return seat->pointer.focus && seat->pointer.focus->keyboard_focus_count != 0; + if (seat->display->relative_pointer_manager) { + bool relative_focus = false; + + if (seat->pointer.focus) { + /* If a seat has both keyboard and pointer capabilities, relative focus will follow the keyboard + * attached to that seat. Otherwise, relative focus will be gained if any other seat has keyboard + * focus on the window with pointer focus. + */ + if (seat->pointer.focus->sdlwindow->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE) { + if (seat->keyboard.wl_keyboard) { + relative_focus = seat->keyboard.focus == seat->pointer.focus; + } else { + relative_focus = seat->pointer.focus->keyboard_focus_count != 0; + } + } else { + relative_focus = SDL_GetMouse()->warp_emulation_active; + } + } + + if (relative_focus) { + if (!seat->pointer.relative_pointer) { + seat->pointer.relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(seat->display->relative_pointer_manager, seat->pointer.wl_pointer); + zwp_relative_pointer_v1_add_listener(seat->pointer.relative_pointer, &relative_pointer_listener, seat); + } + } else if (seat->pointer.relative_pointer) { + zwp_relative_pointer_v1_destroy(seat->pointer.relative_pointer); + seat->pointer.relative_pointer = NULL; + } } } @@ -3532,11 +3523,10 @@ static void Wayland_SeatUpdateKeyboardGrab(SDL_WaylandSeat *seat) void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat) { SDL_VideoData *display = seat->display; + Wayland_SeatUpdateRelativePointer(seat); if (display->pointer_constraints) { - const bool has_relative_focus = Wayland_SeatHasRelativePointerFocus(seat); - - if (seat->pointer.locked_pointer && (!display->relative_mode_enabled || !has_relative_focus)) { + if (seat->pointer.locked_pointer && !seat->pointer.relative_pointer) { zwp_locked_pointer_v1_destroy(seat->pointer.locked_pointer); seat->pointer.locked_pointer = NULL; @@ -3546,7 +3536,7 @@ void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat) if (seat->pointer.wl_pointer) { // If relative mode is active, and the pointer focus matches the keyboard focus, lock it. - if (seat->display->relative_mode_enabled && has_relative_focus) { + if (seat->pointer.relative_pointer) { if (!seat->pointer.locked_pointer) { // Creating a lock on a surface with an active confinement region on the same seat is a protocol error. if (seat->pointer.confined_pointer) { diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index a6c1eed589..a80793922f 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -191,7 +191,6 @@ extern int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS); extern void Wayland_DisplayInitInputTimestampManager(SDL_VideoData *display); extern void Wayland_DisplayInitCursorShapeManager(SDL_VideoData *display); -extern void Wayland_DisplayInitRelativePointerManager(SDL_VideoData *display); extern void Wayland_DisplayInitTabletManager(SDL_VideoData *display); extern void Wayland_DisplayInitDataDeviceManager(SDL_VideoData *display); extern void Wayland_DisplayInitPrimarySelectionDeviceManager(SDL_VideoData *display); @@ -201,7 +200,6 @@ extern void Wayland_DisplayCreateTextInputManager(SDL_VideoData *d, uint32_t id) extern void Wayland_DisplayCreateSeat(SDL_VideoData *display, struct wl_seat *wl_seat, Uint32 id); extern void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events); -extern bool Wayland_SeatHasRelativePointerFocus(SDL_WaylandSeat *seat); extern void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat); extern void Wayland_DisplayUpdatePointerGrabs(SDL_VideoData *display, SDL_WindowData *window); extern void Wayland_DisplayUpdateKeyboardGrabs(SDL_VideoData *display, SDL_WindowData *window); diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index a4f354cdb8..39b3fcb8c7 100644 --- a/src/video/wayland/SDL_waylandmouse.c +++ b/src/video/wayland/SDL_waylandmouse.c @@ -881,8 +881,7 @@ static bool Wayland_WarpMouseRelative(SDL_Window *window, float x, float y) if (d->pointer_constraints) { wl_list_for_each (seat, &d->seat_list, link) { - if (wind == seat->pointer.focus || - (!seat->pointer.focus && wind == seat->keyboard.focus)) { + if (wind == seat->pointer.focus) { Wayland_SeatWarpMouse(seat, wind, x, y); } } @@ -939,7 +938,7 @@ static bool Wayland_SetRelativeMouseMode(bool enabled) return SDL_SetError("Failed to enable relative mode: compositor lacks support for the required zwp_pointer_constraints_v1 protocol"); } - data->relative_mode_enabled = enabled; + // Windows have a relative mode flag, so just update the grabs on a state change. Wayland_DisplayUpdatePointerGrabs(data, NULL); return true; } @@ -1122,13 +1121,10 @@ void Wayland_SeatUpdateCursor(SDL_WaylandSeat *seat) SDL_WindowData *pointer_focus = seat->pointer.focus; if (pointer_focus && mouse->cursor_visible) { - const bool has_relative_focus = Wayland_SeatHasRelativePointerFocus(seat); - - if (!seat->display->relative_mode_enabled || !has_relative_focus || !mouse->relative_mode_hide_cursor) { + if (!seat->pointer.relative_pointer || !mouse->relative_mode_hide_cursor) { const SDL_HitTestResult rc = pointer_focus->hit_test_result; - if ((seat->display->relative_mode_enabled && has_relative_focus) || - rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) { + if (seat->pointer.relative_pointer || rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) { Wayland_SeatSetCursor(seat, mouse->cur_cursor); } else { Wayland_SeatSetCursor(seat, sys_cursors[rc]); diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index b615bf7079..1713d493ab 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -1267,7 +1267,6 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); } else if (SDL_strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) { d->relative_pointer_manager = wl_registry_bind(d->registry, id, &zwp_relative_pointer_manager_v1_interface, 1); - Wayland_DisplayInitRelativePointerManager(d); } else if (SDL_strcmp(interface, "zwp_pointer_constraints_v1") == 0) { d->pointer_constraints = wl_registry_bind(d->registry, id, &zwp_pointer_constraints_v1_interface, 1); } else if (SDL_strcmp(interface, "zwp_keyboard_shortcuts_inhibit_manager_v1") == 0) { diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index 33f2091966..e702a8e6a3 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -96,9 +96,7 @@ struct SDL_VideoData int output_count; int output_max; - bool relative_mode_enabled; bool display_externally_owned; - bool scale_to_display_enabled; }; diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 7a06d37dbf..eafe9f8b88 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -2209,7 +2209,7 @@ static const struct xdg_activation_token_v1_listener activation_listener_xdg = { * * As you might expect from Wayland, the general policy is to go with #2 unless * the client can prove to the compositor beyond a reasonable doubt that raising - * the window will not be malicuous behavior. + * the window will not be malicious behavior. * * For SDL this means RaiseWindow and FlashWindow both use the same protocol, * but in different ways: RaiseWindow will provide as _much_ information as From 3dceb728b71ff274826e7daa58a01f1b207dc796 Mon Sep 17 00:00:00 2001 From: Caleb Heuer Date: Fri, 16 May 2025 13:08:46 -0600 Subject: [PATCH 81/83] Suppress spammy gamepad and joystick update events --- src/events/SDL_events.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index 950575c13a..802c794e91 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -895,8 +895,12 @@ static void SDL_LogEvent(const SDL_Event *event) (event->type == SDL_EVENT_FINGER_MOTION) || (event->type == SDL_EVENT_PEN_AXIS) || (event->type == SDL_EVENT_PEN_MOTION) || - (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION) || + (event->type == SDL_EVENT_GAMEPAD_AXIS_MOTION) || (event->type == SDL_EVENT_GAMEPAD_SENSOR_UPDATE) || + (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION) || + (event->type == SDL_EVENT_GAMEPAD_UPDATE_COMPLETE) || + (event->type == SDL_EVENT_JOYSTICK_AXIS_MOTION) || + (event->type == SDL_EVENT_JOYSTICK_UPDATE_COMPLETE) || (event->type == SDL_EVENT_SENSOR_UPDATE))) { return; } From d6a99752b7e14223f7fb84405e53adb18978d384 Mon Sep 17 00:00:00 2001 From: Eduard Gushchin <44260780+edwardgushchin@users.noreply.github.com> Date: Fri, 16 May 2025 16:09:00 +0300 Subject: [PATCH 82/83] Fix properties name in SDL_CreateGPUDeviceWithProperties --- include/SDL3/SDL_gpu.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index a99822aba2..17e5a6cbe2 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2256,20 +2256,20 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * * With the Vulkan renderer: * - * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOL`: Enable + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOLEAN`: Enable * device feature shaderClipDistance. If disabled, clip distances are not * supported in shader code: gl_ClipDistance[] built-ins of GLSL, * SV_ClipDistance0/1 semantics of HLSL and [[clip_distance]] attribute of * Metal. Defaults to true. - * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOL`: Enable device + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOLEAN`: Enable device * feature depthClamp. If disabled, there is no depth clamp support and * enable_depth_clip in SDL_GPURasterizerState must always be set to true. * Defaults to true. - * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOL`: Enable device + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN`: Enable device * feature drawIndirectFirstInstance. If disabled, the argument * first_instance of SDL_GPUIndirectDrawCommand must be set to zero. * Defaults to true. - * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOL`: Enable device + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN`: Enable device * feature samplerAnisotropy. If disabled, enable_anisotropy of * SDL_GPUSamplerCreateInfo must be set to false. Defaults to true. * From 514d96de07c3b1b8603ee4701229bb055a1dd2e3 Mon Sep 17 00:00:00 2001 From: SDL Wiki Bot Date: Sat, 17 May 2025 00:15:49 +0000 Subject: [PATCH 83/83] Sync SDL3 wiki -> header [ci skip] --- include/SDL3/SDL_gpu.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 17e5a6cbe2..ae1cdf0f2c 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2265,12 +2265,12 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * feature depthClamp. If disabled, there is no depth clamp support and * enable_depth_clip in SDL_GPURasterizerState must always be set to true. * Defaults to true. - * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN`: Enable device - * feature drawIndirectFirstInstance. If disabled, the argument + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN`: Enable + * device feature drawIndirectFirstInstance. If disabled, the argument * first_instance of SDL_GPUIndirectDrawCommand must be set to zero. * Defaults to true. - * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN`: Enable device - * feature samplerAnisotropy. If disabled, enable_anisotropy of + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN`: Enable + * device feature samplerAnisotropy. If disabled, enable_anisotropy of * SDL_GPUSamplerCreateInfo must be set to false. Defaults to true. * * \param props the properties to use.