diff --git a/.github/workflows/create-test-plan.py b/.github/workflows/create-test-plan.py index 8048e2bc32..04f4c10923 100755 --- a/.github/workflows/create-test-plan.py +++ b/.github/workflows/create-test-plan.py @@ -113,7 +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", ), + "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, ), 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/build-scripts/wikiheaders.pl b/build-scripts/wikiheaders.pl index 019304e9b0..506ad22dd4 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; @@ -427,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. @@ -1033,7 +1033,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 +2081,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 +2694,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 =~ /\A(README\-.*?)\.md\Z/) { + push @pages, $1; } } closedir(DH); - open(FH, '>', "$wikireadmepath/FrontPage.md") or die("Can't open '$wikireadmepath/FrontPage.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 = $_; 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-documentation-rules.md b/docs/README-documentation-rules.md index 757a3c5fcc..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 @@ -340,7 +350,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. diff --git a/docs/README-emscripten.md b/docs/README-emscripten.md index 2b8146893a..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. @@ -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 ``` diff --git a/docs/README-platforms.md b/docs/README-platforms.md index aee044c1f6..45c581185c 100644 --- a/docs/README-platforms.md +++ b/docs/README-platforms.md @@ -13,7 +13,7 @@ SDL3 has been known to work on the following platforms at some point: - [macOS](README-macos.md) (10.14 and later) - [NetBSD](README-bsd.md) - [Nintendo Switch](README-switch.md) (Separate NDA-only fork) -- [Nintendo 3DS](README-3ds.md) (Homebrew) +- [Nintendo 3DS](README-n3ds.md) (Homebrew) - [OpenBSD](README-bsd.md) - [PlayStation 2](README-ps2.md) (Homebrew) - [PlayStation 4](README-ps4.md) (Separate NDA-only fork) 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/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 diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index c4e96ae2a2..ae1cdf0f2c 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_ @@ -1656,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 { @@ -2216,6 +2254,25 @@ 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_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_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_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_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. @@ -2230,17 +2287,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. @@ -2986,6 +3047,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. @@ -4013,7 +4077,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 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_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 */ diff --git a/include/SDL3/SDL_process.h b/include/SDL3/SDL_process.h index 0e19bfff1d..57e3afd94c 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/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: * diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index f11066c21b..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. } } @@ -1523,8 +1530,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 +1543,16 @@ 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; + 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); 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 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) 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 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/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/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/cpuinfo/SDL_cpuinfo.c b/src/cpuinfo/SDL_cpuinfo.c index 81d1e914ce..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) +#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! */ @@ -344,6 +348,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); 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; 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; } 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/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/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c index 41ca388553..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 @@ -450,7 +558,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 +573,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]; } } @@ -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, @@ -1522,30 +1637,47 @@ 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; + } } if (depth_stencil_target_info != NULL) { @@ -1553,10 +1685,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 || @@ -1564,6 +1698,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; } } } @@ -1756,7 +1891,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 +1975,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( @@ -2074,6 +2217,16 @@ 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!"); + 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; + } } // TODO: validate buffer usage? @@ -2605,11 +2758,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..d4f532fde6 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 @@ -1140,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 a1e234d6d2..2f7bd82b3f 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; @@ -1088,6 +1088,7 @@ struct VulkanRenderer VkPhysicalDevice physicalDevice; VkPhysicalDeviceProperties2KHR physicalDeviceProperties; VkPhysicalDeviceDriverPropertiesKHR physicalDeviceDriverProperties; + VkPhysicalDeviceFeatures desiredDeviceFeatures; VkDevice logicalDevice; Uint8 integratedMemoryNotification; Uint8 outOfDeviceLocalMemoryWarning; @@ -3164,7 +3165,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 +3193,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 +3203,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 +4784,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 +4809,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; @@ -8149,7 +8158,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; @@ -10020,7 +10029,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 +10570,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; @@ -11212,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; } @@ -11433,7 +11444,6 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( { VkResult vulkanResult; VkDeviceCreateInfo deviceCreateInfo; - VkPhysicalDeviceFeatures desiredDeviceFeatures; VkPhysicalDeviceFeatures haveDeviceFeatures; VkPhysicalDevicePortabilitySubsetFeaturesKHR portabilityFeatures; const char **deviceExtensions; @@ -11457,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; } @@ -11513,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, @@ -11598,11 +11599,11 @@ 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; - Uint8 result; + bool result = false; if (_this->Vulkan_CreateSurface == NULL) { return false; @@ -11612,16 +11613,27 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) return false; } - renderer = (VulkanRenderer *)SDL_malloc(sizeof(VulkanRenderer)); - SDL_memset(renderer, '\0', sizeof(VulkanRenderer)); + 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; - result = VULKAN_INTERNAL_PrepareVulkan(renderer); + // 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 (result) { - renderer->vkDestroyInstance(renderer->instance, NULL); + result = VULKAN_INTERNAL_PrepareVulkan(renderer); + if (result) { + renderer->vkDestroyInstance(renderer->instance, NULL); + } + SDL_free(renderer); } - SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); + return result; } @@ -11642,12 +11654,27 @@ 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; + // 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(); 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 diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index 5f08f94752..d5541ae649 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -779,6 +779,22 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) } break; } + } else if (vendor == USB_VENDOR_8BITDO && + (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)) { + 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 +818,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 +1311,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_gamepad_db.h b/src/joystick/SDL_gamepad_db.h index 0bc9e708e1..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,", @@ -868,7 +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,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,", diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index df6988909a..3caf227f01 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -3175,11 +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) -{ - return vendor_id == USB_VENDOR_8BITDO && (product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS); -} - 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/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; } diff --git a/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/src/joystick/hidapi/SDL_hidapi_8bitdo.c index ca60118006..2dc5f92fda 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,9 +117,30 @@ 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); + 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) @@ -128,15 +154,40 @@ 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; + 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]; - int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 80); - if (size >= ULTIMATE2_WIRELESS_V103_REPORT_SIZE) { + 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; } } + 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); } @@ -162,7 +213,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; @@ -232,11 +290,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] != 0x03 && data[0] != 0x01) { + + 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; } @@ -297,7 +439,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)); } @@ -355,7 +497,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]; @@ -414,7 +555,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/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c index e471267c38..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" @@ -38,6 +39,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) @@ -63,14 +65,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 @@ -205,9 +218,10 @@ /* 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 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) @@ -216,9 +230,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 +256,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 +289,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,46 +320,52 @@ 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; + Uint8 extra_buttons; + Uint8 extra_axes; } 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 }, - /* - * 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, - .quirks = GIP_QUIRK_BROKEN_METADATA }, + { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_CLASSIC, 0, + .quirks = GIP_QUIRK_NO_IMPULSE_VIBRATION }, - { USB_VENDOR_RAZER, USB_PRODUCT_RAZER_ATROX, + { 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 }, + { 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}, }; @@ -374,7 +398,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 @@ -393,14 +417,18 @@ typedef struct GIP_Metadata Uint16 version_minor; GIP_DeviceMetadata device; - + Uint8 num_messages; 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; + SDL_KeyboardID keyboard; Uint8 fragment_message; Uint16 total_length; @@ -412,9 +440,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 +463,35 @@ typedef struct GIP_Device Uint8 last_input[64]; - bool reset_for_metadata; - GIP_DeviceType device_type; + Uint8 last_modifiers; + bool capslock; + SDL_Keycode last_key; + Uint32 altcode; + int altcode_digit; + + GIP_AttachmentType attachment_type; GIP_PaddleFormat paddle_format; Uint32 features; Uint32 quirks; 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 +{ + SDL_HIDAPI_Device *device; + + Uint64 hello_deadline; + bool got_hello; + bool reset_for_metadata; + int timeout; + + GIP_Attachment *attachments[MAX_ATTACHMENTS]; } GIP_Device; typedef struct GIP_HelloDevice @@ -509,7 +555,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 +592,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,64 +621,76 @@ 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; } } else { - seq = device->seq_vendor++; + if (command == GIP_CMD_DIRECT_MOTOR) { + // The motor sequence number is optional and always works with 0 + return 0; + } + + 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]; } + + attachment->extra_buttons = quirks[i].extra_buttons; + attachment->extra_axes = quirks[i].extra_axes; break; } } @@ -681,16 +742,34 @@ 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, + false, + NULL, + NULL); +} + +static bool GIP_SendVendorMessage( + GIP_Attachment *attachment, + Uint8 message_type, + Uint8 flags, + const Uint8 *bytes, + int num_bytes) +{ + return GIP_SendRawMessage(attachment->device, + message_type, + flags, + GIP_SequenceNext(attachment, message_type, false), bytes, num_bytes, true, @@ -698,22 +777,9 @@ static bool GIP_SendSystemMessage( NULL); } -static bool GIP_SendVendorMessage( - GIP_Device *device, - Uint8 message_type, - Uint8 flags, - const Uint8 *bytes, - int num_bytes) +static bool GIP_AttachmentIsController(GIP_Attachment *attachment) { - return GIP_SendRawMessage(device, - message_type, - flags, - GIP_SequenceNext(device, message_type, false), - bytes, - num_bytes, - true, - NULL, - NULL); + return attachment->attachment_type != GIP_TYPE_CHATPAD; } static void GIP_MetadataFree(GIP_Metadata *metadata) @@ -792,7 +858,12 @@ 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); +#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); } } @@ -809,7 +880,12 @@ 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); +#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); } } @@ -870,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 } } @@ -913,9 +992,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; @@ -1008,24 +1094,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 @@ -1033,7 +1120,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, @@ -1046,7 +1133,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, @@ -1054,36 +1141,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, @@ -1092,102 +1183,96 @@ 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 (GIP_SupportsVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, false)) { + GIP_SendVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, 0, NULL, 0); + } + + 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; } -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->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; + 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) { - int seq; + 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); - /* 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 (SDL_IsJoystickXboxSeriesX(device->device->vendor_id, device->device->product_id)) { - device->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP; - } - - GIP_HandleQuirks(device); - - if (GIP_SupportsSystemMessage(device, GIP_CMD_FIRMWARE, false)) { - 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); + if (SDL_IsJoystickXboxSeriesX(attachment->device->device->vendor_id, attachment->device->device->product_id)) { + attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP; } } - device->got_metadata = GIP_METADATA_FAKED; - device->hello_deadline = 0; - return HIDAPI_JoystickConnected(device->device, NULL); + GIP_HandleQuirks(attachment); + + if (GIP_SupportsSystemMessage(attachment, GIP_CMD_FIRMWARE, false)) { + GIP_SendQueryFirmware(attachment, 2); + } + + 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) @@ -1198,7 +1283,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) @@ -1281,28 +1366,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) { @@ -1345,86 +1432,89 @@ 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) { 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; - 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; + } } + 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 @@ -1441,23 +1531,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; } } @@ -1466,26 +1556,28 @@ 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; } } - 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."); } - 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) @@ -1496,7 +1588,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) @@ -1504,15 +1596,15 @@ 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; } - if (bytes[1] == VK_LWIN) { + if (bytes[1] == VK_LWIN) { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (bytes[0] & 0x01) != 0); } @@ -1520,7 +1612,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) @@ -1531,7 +1623,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) @@ -1558,20 +1650,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"); @@ -1580,7 +1672,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) @@ -1588,55 +1680,141 @@ 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) { - // 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( - GIP_Device *device, + GIP_Attachment *attachment, const GIP_Header *header, const Uint8 *bytes, int num_bytes) @@ -1652,8 +1830,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 @@ -1664,39 +1845,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)); @@ -1705,7 +1861,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) { @@ -1722,7 +1878,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 */ @@ -1734,7 +1890,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); @@ -1753,72 +1918,208 @@ 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); + } +} + +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 ((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) { + 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, - 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); + (Uint8) (attachment->extra_button_idx + i), + ((bytes[i / 8 + 3] & (1u << i)) != 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); + } else { + i += 8; } } - - if ((device->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) && num_bytes >= 32) { + + /* 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, + 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; + } + } + + 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; + case GIP_TYPE_FLIGHT_STICK: + GIP_HandleFlightStickReport(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; @@ -1827,22 +2128,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) @@ -1853,7 +2154,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) @@ -1864,7 +2165,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) @@ -1875,7 +2176,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) @@ -1886,12 +2187,24 @@ 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 (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", header->message_type); @@ -1899,27 +2212,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", @@ -1928,29 +2241,46 @@ 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; + 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; + 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, @@ -1959,7 +2289,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; @@ -1967,9 +2297,11 @@ 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; + return; } header.message_type = bytes[0]; @@ -1978,6 +2310,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); @@ -1987,157 +2321,161 @@ 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; + return; } - 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", 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, device->total_length); - return -1; + header.length, attachment->total_length); + return; } - 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); - return -1; + header.message_type, attachment->fragment_message); + GIP_FragmentFailed(attachment, &header); + return; } 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); - return GIP_Acknowledge(device, + fragment_offset, attachment->fragment_offset); + 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)); + 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, device->total_length); - GIP_FragmentFailed(device, &header); - return -1; + fragment_offset + header.length, attachment->total_length); + GIP_FragmentFailed(attachment, &header); + return; } - 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", header.length + offset, num_bytes); - return -1; + return; } else { 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)) { GIP_Acknowledge(device, &header, (Uint32) fragment_offset, bytes_remaining); } - return offset + (Uint16) header.length; } 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; } @@ -2182,20 +2520,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; } @@ -2215,44 +2554,70 @@ 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++; } + 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; @@ -2260,38 +2625,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; } @@ -2300,10 +2685,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(); } @@ -2312,7 +2701,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; @@ -2333,47 +2722,64 @@ 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; - while ((num_bytes = SDL_hid_read_timeout(device->dev, bytes, sizeof(bytes), 0)) > 0) { - int parsed = GIP_ReceivePacket(ctx, bytes, num_bytes); - if (parsed <= 0) { - break; - } + while ((num_bytes = SDL_hid_read_timeout(device->dev, bytes, sizeof(bytes), ctx->timeout)) > 0) { + ctx->timeout = 0; + GIP_ReceivePacket(ctx, bytes, num_bytes); } 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 < 3) { + 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); + GIP_SendInitSequence(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); } @@ -2384,8 +2790,24 @@ 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; + } + if (attachment->keyboard) { + SDL_RemoveKeyboard(attachment->keyboard, true); + } + 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 6692cd412d..26059aa289 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -60,12 +60,19 @@ #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_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 #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 @@ -96,6 +103,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 @@ -124,6 +132,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 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/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); } 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/src/render/SDL_render.c b/src/render/SDL_render.c index 342b25b2ec..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 (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; 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; 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"); } 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/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]; 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) { 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) { diff --git a/src/video/openvr/SDL_openvrvideo.c b/src/video/openvr/SDL_openvrvideo.c index a89467cc1b..a24cd02fbe 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,22 +539,22 @@ 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 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 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) @@ -710,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. diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 2ed845bcf8..ec01cbc0a2 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); } @@ -901,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 { @@ -1153,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 = { @@ -1243,6 +1247,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 +1274,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 +1315,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 +1903,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); } } @@ -2092,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. @@ -2254,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) { @@ -3416,6 +3372,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) { @@ -3475,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; + } } } @@ -3517,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; @@ -3531,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 3c3aed69d5..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,10 +200,10 @@ 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); +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_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index 5a2a00d02f..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; } @@ -1121,14 +1120,11 @@ void Wayland_SeatUpdateCursor(SDL_WaylandSeat *seat) SDL_Mouse *mouse = SDL_GetMouse(); SDL_WindowData *pointer_focus = seat->pointer.focus; - if (pointer_focus) { - const bool has_relative_focus = Wayland_SeatHasRelativePointerFocus(seat); - - if (!seat->display->relative_mode_enabled || !has_relative_focus || !mouse->relative_mode_hide_cursor) { + if (pointer_focus && mouse->cursor_visible) { + 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 516419c281..1713d493ab 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; @@ -1263,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 b019eff2f9..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 @@ -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 { 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_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: 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); diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 706ce4d9e2..84e466255d 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); } @@ -997,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); } } @@ -1084,16 +1077,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 +1322,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; @@ -1360,8 +1341,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; @@ -1387,17 +1370,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_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; diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index f371f519d6..b5dab99b5f 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; @@ -280,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); 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; 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 };