diff --git a/.github/workflows/create-test-plan.py b/.github/workflows/create-test-plan.py index 09972427e2..544e9372c5 100755 --- a/.github/workflows/create-test-plan.py +++ b/.github/workflows/create-test-plan.py @@ -54,6 +54,7 @@ class SdlPlatform(Enum): Riscos = "riscos" FreeBSD = "freebsd" NetBSD = "netbsd" + OpenBSD = "openbsd" NGage = "ngage" Harmony = "harmony" @@ -141,6 +142,7 @@ JOB_SPECS = { "vita-pvr": JobSpec(name="Sony PlayStation Vita (GLES w/ PVR_PSP2)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Vita, artifact="SDL-vita-pvr", container="vitasdk/vitasdk:latest", vita_gles=VitaGLES.Pvr, ), "riscos": JobSpec(name="RISC OS", os=JobOs.UbuntuLatest, platform=SdlPlatform.Riscos, artifact="SDL-riscos", container="riscosdotinfo/riscos-gccsdk-4.7:latest", ), "netbsd": JobSpec(name="NetBSD", os=JobOs.UbuntuLatest, platform=SdlPlatform.NetBSD, artifact="SDL-netbsd-x64", ), + "openbsd": JobSpec(name="OpenBSD", os=JobOs.UbuntuLatest, platform=SdlPlatform.OpenBSD, artifact="SDL-openbsd-x64", ), "freebsd": JobSpec(name="FreeBSD", os=JobOs.UbuntuLatest, platform=SdlPlatform.FreeBSD, artifact="SDL-freebsd-x64", ), "ngage": JobSpec(name="N-Gage", os=JobOs.WindowsLatest, platform=SdlPlatform.NGage, artifact="SDL-ngage", ), "harmony-arm64": JobSpec(name="Harmony (Arm64)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Harmony, artifact="SDL-harmony-arm64", harmony_arch="arm64-v8a"), @@ -566,6 +568,10 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta "testmultiaudio-apk", "testsprite-apk", ] + + # -fPIC is required after updating NDK from 21 to 28 + job.cflags.append("-fPIC") + job.cxxflags.append("-fPIC") case SdlPlatform.Emscripten: job.clang_tidy = False # clang-tidy does not understand -gsource-map job.shared = False @@ -732,7 +738,7 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta )) job.cmake_toolchain_file = "/home/riscos/env/toolchain-riscos.cmake" job.static_lib = StaticLibType.A - case SdlPlatform.FreeBSD | SdlPlatform.NetBSD: + case SdlPlatform.FreeBSD | SdlPlatform.NetBSD | SdlPlatform.OpenBSD: job.cpactions = True job.no_cmake = True job.run_tests = False @@ -756,6 +762,12 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta job.cpactions_arch = "x86-64" job.cpactions_setup_cmd = "export PATH=\"/usr/pkg/sbin:/usr/pkg/bin:/sbin:$PATH\"; export PKG_CONFIG_PATH=\"/usr/pkg/lib/pkgconfig\";export PKG_PATH=\"https://cdn.netBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r|cut -f \"1 2\" -d.)/All/\";echo \"PKG_PATH=$PKG_PATH\";echo \"uname -a -> \"$(uname -a)\"\";sudo -E sysctl -w security.pax.aslr.enabled=0;sudo -E sysctl -w security.pax.aslr.global=0;sudo -E pkgin clean;sudo -E pkgin update" job.cpactions_install_cmd = "sudo -E pkgin -y install cmake dbus pkgconf ninja-build pulseaudio libxkbcommon wayland wayland-protocols libinotify libusb1" + case SdlPlatform.OpenBSD: + job.cpactions_os = "openbsd" + job.cpactions_version = "7.4" + job.cpactions_arch = "x86-64" + job.cpactions_setup_cmd = "sudo pkg_add -u" + job.cpactions_install_cmd = "sudo pkg_add cmake ninja pkgconf wayland wayland-protocols xwayland libxkbcommon libinotify pulseaudio dbus ibus" case SdlPlatform.NGage: build_parallel = False job.cmake_build_type = "Release" diff --git a/.github/workflows/generic.yml b/.github/workflows/generic.yml index 57ba44b088..4ca6719139 100644 --- a/.github/workflows/generic.yml +++ b/.github/workflows/generic.yml @@ -79,7 +79,7 @@ jobs: id: setup-ndk with: local-cache: true - ndk-version: r21e + ndk-version: r28c - name: 'Configure Android NDK variables' if: ${{ matrix.platform.android-ndk }} shell: sh @@ -414,6 +414,14 @@ jobs: build-scripts/test-versioning.sh python build-scripts/check_android_jni.py python build-scripts/check_stdlib_usage.py + - name: 'Verify alignment of Android test apks' + if: ${{ matrix.platform.android-ndk && !matrix.platform.no-cmake }} + run: | + find ./ -iname '*.apk' | xargs -L1 ./build-scripts/check_elf_alignment.sh + - name: 'Verify alignment of Android .so files' + if: ${{ matrix.platform.android-ndk && !matrix.platform.no-cmake }} + run: | + find ./ -iname '*.so' | xargs -L1 ./build-scripts/check_elf_alignment.sh - name: 'Upload binary package' uses: actions/upload-artifact@v4 if: ${{ always() && matrix.platform.artifact != '' && (steps.package.outcome == 'success' || steps.cpactions.outcome == 'success') && (matrix.platform.enable-artifacts || steps.tests.outcome == 'failure') }} @@ -436,4 +444,4 @@ jobs: with: if-no-files-found: error name: '${{ matrix.platform.artifact }}-apks' - path: build/test/*.apk \ No newline at end of file + path: build/test/*.apk diff --git a/.wikiheaders-options b/.wikiheaders-options index 38a2f2731c..696a08c70b 100644 --- a/.wikiheaders-options +++ b/.wikiheaders-options @@ -31,3 +31,11 @@ quickreftitle = SDL3 API Quick Reference quickrefurl = https://libsdl.org/ quickrefdesc = The latest version of this document can be found at https://wiki.libsdl.org/SDL3/QuickReference quickrefmacroregex = \A(SDL_PLATFORM_.*|SDL_.*_INTRINSICS|SDL_Atomic...Ref|SDL_assert.*?|SDL_COMPILE_TIME_ASSERT|SDL_arraysize|SDL_Swap[BL]E\d\d|SDL_[a-z]+_cast)\Z + +envvarenabled = 1 +envvartitle = SDL3 Environment Variables +envvardesc = SDL3 can be controlled by the user, externally, with environment variables. They are all operate exactly like the [hints you can get and set programmatically](CategoryHints), but named without the `_HINT` part (so `"SDL_HINT_A"` would be environment variable `"SDL_A"`).\n\nThis list matches the latest in SDL3's revision control. +envvarsymregex = \ASDL_HINT_(.*)\Z +envvarsymreplace = SDL_$1 + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 735ece5e4f..0100ae8999 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,12 @@ include("${SDL3_SOURCE_DIR}/cmake/PreseedNokiaNGageCache.cmake") SDL_DetectCompiler() SDL_DetectTargetCPUArchitectures(SDL_CPUS) +if(APPLE AND CMAKE_OSX_ARCHITECTURES) + list(LENGTH CMAKE_OSX_ARCHITECTURES _num_arches) + if(_num_arches GREATER 1) + set(APPLE_MULTIARCH TRUE) + endif() +endif() # Increment this if there is an incompatible change - but if that happens, # we should rename the library from SDL3 to SDL4, at which point this would @@ -253,6 +259,7 @@ define_sdl_subsystem(Hidapi) define_sdl_subsystem(Power) define_sdl_subsystem(Sensor) define_sdl_subsystem(Dialog) +define_sdl_subsystem(Tray) cmake_dependent_option(SDL_FRAMEWORK "Build SDL libraries as Apple Framework" OFF "APPLE" OFF) if(SDL_FRAMEWORK) @@ -301,6 +308,7 @@ dep_option(SDL_ARMNEON "Use NEON assembly routines" ON "SDL_ASSEMBLY dep_option(SDL_LSX "Use LSX assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_LOONGARCH64" OFF) dep_option(SDL_LASX "Use LASX assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_LOONGARCH64" OFF) +dep_option(SDL_DLOPEN_NOTES "Record dlopen dependencies in .note.dlopen section" TRUE UNIX_SYS OFF) set_option(SDL_LIBC "Use the system C library" ${SDL_LIBC_DEFAULT}) set_option(SDL_SYSTEM_ICONV "Use iconv() from system-installed libraries" ${SDL_SYSTEM_ICONV_DEFAULT}) set_option(SDL_LIBICONV "Prefer iconv() from libiconv, if available, over libc version" OFF) @@ -352,7 +360,7 @@ dep_option(SDL_WASAPI "Use the Windows WASAPI audio driver" ON "WIN dep_option(SDL_RENDER_D3D "Enable the Direct3D 9 render driver" ON "SDL_RENDER;SDL_DIRECTX" OFF) dep_option(SDL_RENDER_D3D11 "Enable the Direct3D 11 render driver" ON "SDL_RENDER;SDL_DIRECTX" OFF) dep_option(SDL_RENDER_D3D12 "Enable the Direct3D 12 render driver" ON "SDL_RENDER;SDL_DIRECTX" OFF) -dep_option(SDL_RENDER_METAL "Enable the Metal render driver" ON "SDL_RENDER;${APPLE}" OFF) +dep_option(SDL_RENDER_METAL "Enable the Metal render driver" ON "SDL_RENDER;APPLE" OFF) dep_option(SDL_RENDER_GPU "Enable the SDL_GPU render driver" ON "SDL_RENDER;SDL_GPU" OFF) dep_option(SDL_VIVANTE "Use Vivante EGL video driver" ON "${UNIX_SYS};SDL_CPU_ARM32" OFF) dep_option(SDL_VULKAN "Enable Vulkan support" ON "SDL_VIDEO;ANDROID OR APPLE OR LINUX OR FREEBSD OR WINDOWS" OFF) @@ -410,6 +418,7 @@ if (NGAGE) set(SDL_DUMMYVIDEO OFF) set(SDL_OFFSCREEN OFF) set(SDL_RENDER_GPU OFF) + set(SDL_TRAY OFF) set(SDL_VIRTUAL_JOYSTICK OFF) endif() @@ -680,7 +689,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -mmmx") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void ints_add(int *dest, int *a, int *b, unsigned size) { for (; size >= 2; size -= 2, dest += 2, a += 2, b += 2) { @@ -690,7 +699,7 @@ if(SDL_ASSEMBLY) int main(int argc, char *argv[]) { ints_add((int*)0, (int*)0, (int*)0, 0); return 0; - }" COMPILER_SUPPORTS_MMX) + }]==] COMPILER_SUPPORTS_MMX) cmake_pop_check_state() if(COMPILER_SUPPORTS_MMX) set(HAVE_MMX TRUE) @@ -701,7 +710,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -msse") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void floats_add(float *dest, float *a, float *b, unsigned size) { for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { @@ -711,7 +720,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { floats_add((float*)0, (float*)0, (float*)0, 0); return 0; - }" COMPILER_SUPPORTS_SSE) + }]==] COMPILER_SUPPORTS_SSE) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE) set(HAVE_SSE TRUE) @@ -722,7 +731,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -msse2") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void doubles_add(double *dest, double *a, double *b, unsigned size) { for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { @@ -732,7 +741,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { doubles_add((double*)0, (double*)0, (double*)0, 0); return 0; - }" COMPILER_SUPPORTS_SSE2) + }]==] COMPILER_SUPPORTS_SSE2) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE2) set(HAVE_SSE2 TRUE) @@ -743,7 +752,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -msse3") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void ints_add(int *dest, int *a, int *b, unsigned size) { for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { @@ -753,7 +762,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { ints_add((int*)0, (int*)0, (int*)0, 0); return 0; - }" COMPILER_SUPPORTS_SSE3) + }]==] COMPILER_SUPPORTS_SSE3) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE3) set(HAVE_SSE3 TRUE) @@ -764,7 +773,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -msse4.1") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void ints_mul(int *dest, int *a, int *b, unsigned size) { for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { @@ -774,7 +783,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { ints_mul((int*)0, (int*)0, (int*)0, 0); return 0; - }" COMPILER_SUPPORTS_SSE4_1) + }]==] COMPILER_SUPPORTS_SSE4_1) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE4_1) set(HAVE_SSE4_1 TRUE) @@ -783,21 +792,16 @@ if(SDL_ASSEMBLY) if(SDL_SSE4_2) cmake_push_check_state() if(USE_GCC OR USE_CLANG OR USE_INTELCC) - string(APPEND CMAKE_REQUIRED_FLAGS " -msse4.2 -mcrc32") + string(APPEND CMAKE_REQUIRED_FLAGS " -msse4.2") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include - unsigned calc_crc32c(const char *text, unsigned len) { - unsigned crc32c = ~0; - for (; len >= 4; len -= 4, text += 4) { - crc32c = (unsigned)_mm_crc32_u32(crc32c, *(unsigned*)text); - } - return crc32c; - } + __m128i bitmask; + char data[16]; int main(int argc, char **argv) { - calc_crc32c(\"SDL_SSE4\",8); + bitmask = _mm_cmpgt_epi64(_mm_set1_epi64x(0), _mm_loadu_si128((void*)data)); return 0; - }" COMPILER_SUPPORTS_SSE4_2) + }]==] COMPILER_SUPPORTS_SSE4_2) cmake_pop_check_state() if(COMPILER_SUPPORTS_SSE4_2) set(HAVE_SSE4_2 TRUE) @@ -808,7 +812,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -mavx") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void floats_add(float *dest, float *a, float *b, unsigned size) { for (; size >= 8; size -= 8, dest += 8, a += 8, b += 8) { @@ -818,7 +822,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { floats_add((float*)0, (float*)0, (float*)0, 0); return 0; - }" COMPILER_SUPPORTS_AVX) + }]==] COMPILER_SUPPORTS_AVX) cmake_pop_check_state() if(COMPILER_SUPPORTS_AVX) set(HAVE_AVX TRUE) @@ -829,7 +833,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -mavx2") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void ints_add(int *dest, int *a, int *b, unsigned size) { for (; size >= 8; size -= 8, dest += 8, a += 8, b += 8) { @@ -839,7 +843,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { ints_add((int*)0, (int*)0, (int*)0, 0); return 0; - }" COMPILER_SUPPORTS_AVX2) + }]==] COMPILER_SUPPORTS_AVX2) cmake_pop_check_state() if(COMPILER_SUPPORTS_AVX2) set(HAVE_AVX2 TRUE) @@ -850,7 +854,7 @@ if(SDL_ASSEMBLY) if(USE_GCC OR USE_CLANG OR USE_INTELCC) string(APPEND CMAKE_REQUIRED_FLAGS " -mavx512f") endif() - check_c_source_compiles(" + check_x86_source_compiles([==[ #include void floats_add(float *dest, float *a, float *b, unsigned size) { for (; size >= 16; size -= 16, dest += 16, a += 16, b += 16) { @@ -860,7 +864,7 @@ if(SDL_ASSEMBLY) int main(int argc, char **argv) { floats_add((float*)0, (float*)0, (float*)0, 0); return 0; - }" COMPILER_SUPPORTS_AVX512F) + }]==] COMPILER_SUPPORTS_AVX512F) cmake_pop_check_state() if(COMPILER_SUPPORTS_AVX512F) set(HAVE_AVX512F TRUE) @@ -868,18 +872,17 @@ if(SDL_ASSEMBLY) endif() if(SDL_ARMNEON) - check_c_source_compiles(" - #include - void floats_add(float *dest, float *a, float *b, unsigned size) { - for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { - vst1q_f32(dest, vaddq_f32(vld1q_f32(a), vld1q_f32(b))); - } + check_arm_source_compiles([==[ + #include + void floats_add(float *dest, float *a, float *b, unsigned size) { + for (; size >= 4; size -= 4, dest += 4, a += 4, b += 4) { + vst1q_f32(dest, vaddq_f32(vld1q_f32(a), vld1q_f32(b))); } - int main(int argc, char *argv[]) { - floats_add((float*)0, (float*)0, (float*)0, 0); - return 0; - }" COMPILER_SUPPORTS_ARMNEON) - + } + int main(int argc, char *argv[]) { + floats_add((float*)0, (float*)0, (float*)0, 0); + return 0; + }]==] COMPILER_SUPPORTS_ARMNEON) if(COMPILER_SUPPORTS_ARMNEON) set(HAVE_ARMNEON TRUE) endif() @@ -1082,8 +1085,10 @@ if(SDL_LIBC) cmake_push_check_state() if(MSVC) string(APPEND CMAKE_REQUIRED_FLAGS " -we4244 -WX") # 'conversion' conversion from 'type1' to 'type2', possible loss of data - else() + elseif(HAVE_GCC_WFLOAT_CONVERSION) string(APPEND CMAKE_REQUIRED_FLAGS " -Wfloat-conversion -Werror") + else() + string(APPEND CMAKE_REQUIRED_FLAGS " -Wconversion -Werror") endif() foreach(math_fn isinf isnan) string(TOUPPER "${math_fn}" MATH_FN) @@ -1121,6 +1126,7 @@ if(SDL_LIBC) check_symbol_exists(gethostname "unistd.h" HAVE_GETHOSTNAME) check_symbol_exists(getpagesize "unistd.h" HAVE_GETPAGESIZE) check_symbol_exists(sigaction "signal.h" HAVE_SIGACTION) + check_symbol_exists(sigtimedwait "signal.h" HAVE_SIGTIMEDWAIT) check_symbol_exists(setjmp "setjmp.h" HAVE_SETJMP) check_symbol_exists(nanosleep "time.h" HAVE_NANOSLEEP) check_symbol_exists(gmtime_r "time.h" HAVE_GMTIME_R) @@ -1759,6 +1765,25 @@ elseif(EMSCRIPTEN) CheckLibUnwind() elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) + + if(SDL_DLOPEN_NOTES) + set(CHECK_ELF_DLNOTES_SRC [==[ + #ifndef __ELF__ + ELF DL notes is only supported on ELF platforms + #endif + __attribute__ ((used,aligned(4),section(".note.dlopen"))) static const struct { + struct { int a; int b; int c; } hdr; char name[4]; __attribute__((aligned(4))) char json[24]; + } dlnote = { { 4, 0x407c0c0aU, 16 }, "FDO", "[\\"a\\":{\\"a\\":\\"1\\",\\"b\\":\\"2\\"}]" }; + int main(int argc, char *argv[]) { + return argc + dlnote.hdr.a; + } + ]==]) + check_c_source_compiles("${CHECK_ELF_DLNOTES_SRC}" COMPILER_SUPPORTS_ELFNOTES) + if(COMPILER_SUPPORTS_ELFNOTES) + set(HAVE_DLOPEN_NOTES TRUE) + endif() + endif() + if(SDL_AUDIO) if(NETBSD) set(SDL_AUDIO_DRIVER_NETBSD 1) @@ -1801,7 +1826,9 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) CheckVivante() CheckVulkan() CheckQNXScreen() + endif() + if(SDL_TRAY) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/tray/unix/*.c" "${SDL3_SOURCE_DIR}/src/tray/unix/*.h" @@ -2221,6 +2248,7 @@ elseif(WINDOWS) #include int main(int argc, char **argv) { return 0; }" HAVE_GAMEINPUT_H ) + check_include_file(dxgi1_5.h HAVE_DXGI1_5_H) check_include_file(dxgi1_6.h HAVE_DXGI1_6_H) check_include_file(tpcshrd.h HAVE_TPCSHRD_H) check_include_file(roapi.h HAVE_ROAPI_H) @@ -2378,7 +2406,9 @@ elseif(WINDOWS) set(HAVE_RENDER_VULKAN TRUE) endif() endif() + endif() + if(SDL_TRAY) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/windows/*.c") set(HAVE_SDL_TRAY TRUE) endif() @@ -2694,11 +2724,11 @@ elseif(APPLE) endif() endif() endif() + endif() - if(MACOS) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/cocoa/*.m") - set(HAVE_SDL_TRAY TRUE) - endif() + if(SDL_TRAY AND MACOS) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/cocoa/*.m") + set(HAVE_SDL_TRAY TRUE) endif() # Minimum version for $ @@ -3417,7 +3447,7 @@ if(WINDOWS) ) set(SDL_PROCESS_WINDOWS 1) set(HAVE_SDL_PROCESS TRUE) -else() +elseif(NOT ANDROID) check_c_source_compiles(" #include #include @@ -4289,6 +4319,7 @@ endif() ##### Uninstall target ##### if(SDL_UNINSTALL) + set(HAVE_UNINSTALL ON) if(NOT TARGET uninstall) configure_file(cmake/cmake_uninstall.cmake.in cmake_uninstall.cmake IMMEDIATE @ONLY) diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index a9024fc683..7bba0226db 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -326,7 +326,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -729,6 +728,7 @@ + @@ -765,6 +765,7 @@ + @@ -889,7 +890,6 @@ - diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index 9a0ce21537..31eb986125 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -80,6 +80,7 @@ + @@ -98,6 +99,7 @@ + @@ -191,7 +193,6 @@ - diff --git a/VisualC-GDK/SDL_test/SDL_test.vcxproj b/VisualC-GDK/SDL_test/SDL_test.vcxproj index 8d5106cbd6..802910de3f 100644 --- a/VisualC-GDK/SDL_test/SDL_test.vcxproj +++ b/VisualC-GDK/SDL_test/SDL_test.vcxproj @@ -192,7 +192,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj b/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj index f600ce203c..5f89c24244 100644 --- a/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj +++ b/VisualC-GDK/tests/testcontroller/testcontroller.vcxproj @@ -268,7 +268,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC-GDK/tests/testgdk/testgdk.vcxproj b/VisualC-GDK/tests/testgdk/testgdk.vcxproj index c16fadefe1..2e3ecf8727 100644 --- a/VisualC-GDK/tests/testgdk/testgdk.vcxproj +++ b/VisualC-GDK/tests/testgdk/testgdk.vcxproj @@ -292,7 +292,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC-GDK/tests/testsprite/testsprite.vcxproj b/VisualC-GDK/tests/testsprite/testsprite.vcxproj index 15ea690af8..0c1ac0622c 100644 --- a/VisualC-GDK/tests/testsprite/testsprite.vcxproj +++ b/VisualC-GDK/tests/testsprite/testsprite.vcxproj @@ -292,7 +292,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/SDL.sln b/VisualC/SDL.sln index b5cf786ae2..d6e894049e 100644 --- a/VisualC/SDL.sln +++ b/VisualC/SDL.sln @@ -131,6 +131,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "01-joystick-polling", "exam EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "02-joystick-events", "examples\input\02-joystick-events\02-joystick-events.vcxproj", "{FCBDF2B2-1129-49AE-9406-3F219E65CA89}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testsoftwaretransparent", "tests\testsoftwaretransparent\testsoftwaretransparent.vcxproj", "{D91C45E2-274E-4C0F-89C7-9986F9A7E85A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -579,6 +581,14 @@ Global {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|Win32.Build.0 = Release|Win32 {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|x64.ActiveCfg = Release|x64 {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|x64.Build.0 = Release|x64 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Debug|Win32.ActiveCfg = Debug|Win32 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Debug|Win32.Build.0 = Debug|Win32 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Debug|x64.ActiveCfg = Debug|x64 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Debug|x64.Build.0 = Debug|x64 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Release|Win32.ActiveCfg = Release|Win32 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Release|Win32.Build.0 = Release|Win32 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Release|x64.ActiveCfg = Release|x64 + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -644,6 +654,7 @@ Global {3DB9B219-769E-43AC-8B8B-319DB6045DCF} = {D1BF59F6-22DC-493B-BDEB-451A50DA793D} {B3852DB7-E925-4026-8B9D-D2272EFEFF3C} = {8DEAE483-FDE7-463F-9FD5-F597BBAED1F9} {FCBDF2B2-1129-49AE-9406-3F219E65CA89} = {8DEAE483-FDE7-463F-9FD5-F597BBAED1F9} + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A} = {D69D5741-611F-4E14-8541-1FEE94F50B5A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C320C9F2-1A8F-41D7-B02B-6338F872BCAD} diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index f59c31f9e2..9514dd5a8d 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -239,7 +239,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) @@ -619,6 +618,7 @@ + @@ -637,6 +637,7 @@ + @@ -744,7 +745,6 @@ - @@ -775,4 +775,4 @@ - \ No newline at end of file + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index 4a1b31c884..2c927ac42c 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -1137,6 +1137,9 @@ loadso\windows + + misc + misc @@ -1233,6 +1236,9 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi @@ -1356,9 +1362,6 @@ video\dummy - - video\windows - video\windows @@ -1618,4 +1621,4 @@ - \ No newline at end of file + diff --git a/VisualC/SDL_test/SDL_test.vcxproj b/VisualC/SDL_test/SDL_test.vcxproj index 4313bbe1f3..1347150c18 100644 --- a/VisualC/SDL_test/SDL_test.vcxproj +++ b/VisualC/SDL_test/SDL_test.vcxproj @@ -158,7 +158,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/checkkeys/checkkeys.vcxproj b/VisualC/tests/checkkeys/checkkeys.vcxproj index 901cdbbd14..b73120e722 100644 --- a/VisualC/tests/checkkeys/checkkeys.vcxproj +++ b/VisualC/tests/checkkeys/checkkeys.vcxproj @@ -189,7 +189,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/loopwave/loopwave.vcxproj b/VisualC/tests/loopwave/loopwave.vcxproj index 6ab91a8223..271ac351ce 100644 --- a/VisualC/tests/loopwave/loopwave.vcxproj +++ b/VisualC/tests/loopwave/loopwave.vcxproj @@ -189,7 +189,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testatomic/testatomic.vcxproj b/VisualC/tests/testatomic/testatomic.vcxproj index abe71ff916..7123ec282c 100644 --- a/VisualC/tests/testatomic/testatomic.vcxproj +++ b/VisualC/tests/testatomic/testatomic.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testautomation/testautomation.vcxproj b/VisualC/tests/testautomation/testautomation.vcxproj index d973c8dc98..dbefb3697d 100644 --- a/VisualC/tests/testautomation/testautomation.vcxproj +++ b/VisualC/tests/testautomation/testautomation.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testcontroller/testcontroller.vcxproj b/VisualC/tests/testcontroller/testcontroller.vcxproj index fbcadfcb82..9efbb6dca8 100644 --- a/VisualC/tests/testcontroller/testcontroller.vcxproj +++ b/VisualC/tests/testcontroller/testcontroller.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testdialog/testdialog.vcxproj b/VisualC/tests/testdialog/testdialog.vcxproj index 88e570f77d..a74d0f1eee 100644 --- a/VisualC/tests/testdialog/testdialog.vcxproj +++ b/VisualC/tests/testdialog/testdialog.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testdraw/testdraw.vcxproj b/VisualC/tests/testdraw/testdraw.vcxproj index de8a31e221..ba9574585e 100644 --- a/VisualC/tests/testdraw/testdraw.vcxproj +++ b/VisualC/tests/testdraw/testdraw.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testfile/testfile.vcxproj b/VisualC/tests/testfile/testfile.vcxproj index dfcd431221..3fc59b0d30 100644 --- a/VisualC/tests/testfile/testfile.vcxproj +++ b/VisualC/tests/testfile/testfile.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testgl/testgl.vcxproj b/VisualC/tests/testgl/testgl.vcxproj index 6c52125201..088ce931a1 100644 --- a/VisualC/tests/testgl/testgl.vcxproj +++ b/VisualC/tests/testgl/testgl.vcxproj @@ -187,7 +187,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testgles2/testgles2.vcxproj b/VisualC/tests/testgles2/testgles2.vcxproj index 728bb2cc79..194042f532 100644 --- a/VisualC/tests/testgles2/testgles2.vcxproj +++ b/VisualC/tests/testgles2/testgles2.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testoverlay/testoverlay.vcxproj b/VisualC/tests/testoverlay/testoverlay.vcxproj index 6d7e2ccb0a..112bf31434 100644 --- a/VisualC/tests/testoverlay/testoverlay.vcxproj +++ b/VisualC/tests/testoverlay/testoverlay.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testpen/testpen.vcxproj b/VisualC/tests/testpen/testpen.vcxproj index 65888443c5..589e605bca 100644 --- a/VisualC/tests/testpen/testpen.vcxproj +++ b/VisualC/tests/testpen/testpen.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testplatform/testplatform.vcxproj b/VisualC/tests/testplatform/testplatform.vcxproj index af8ae0cdbc..c0aba9f001 100644 --- a/VisualC/tests/testplatform/testplatform.vcxproj +++ b/VisualC/tests/testplatform/testplatform.vcxproj @@ -195,7 +195,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testpower/testpower.vcxproj b/VisualC/tests/testpower/testpower.vcxproj index cacf3621d5..89fc2ea9b4 100644 --- a/VisualC/tests/testpower/testpower.vcxproj +++ b/VisualC/tests/testpower/testpower.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testrendertarget/testrendertarget.vcxproj b/VisualC/tests/testrendertarget/testrendertarget.vcxproj index e59bb6ba81..b487b7e4fb 100644 --- a/VisualC/tests/testrendertarget/testrendertarget.vcxproj +++ b/VisualC/tests/testrendertarget/testrendertarget.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testrumble/testrumble.vcxproj b/VisualC/tests/testrumble/testrumble.vcxproj index 0ae100dc36..78a666f558 100644 --- a/VisualC/tests/testrumble/testrumble.vcxproj +++ b/VisualC/tests/testrumble/testrumble.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testscale/testscale.vcxproj b/VisualC/tests/testscale/testscale.vcxproj index c8900f361b..dbb0e4cd4b 100644 --- a/VisualC/tests/testscale/testscale.vcxproj +++ b/VisualC/tests/testscale/testscale.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testsensor/testsensor.vcxproj b/VisualC/tests/testsensor/testsensor.vcxproj index f0a9af8ac3..d749638443 100644 --- a/VisualC/tests/testsensor/testsensor.vcxproj +++ b/VisualC/tests/testsensor/testsensor.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testshape/testshape.vcxproj b/VisualC/tests/testshape/testshape.vcxproj index 1490483731..97cb7bbbcf 100644 --- a/VisualC/tests/testshape/testshape.vcxproj +++ b/VisualC/tests/testshape/testshape.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testsoftwaretransparent/testsoftwaretransparent.vcxproj b/VisualC/tests/testsoftwaretransparent/testsoftwaretransparent.vcxproj new file mode 100644 index 0000000000..1970a3b334 --- /dev/null +++ b/VisualC/tests/testsoftwaretransparent/testsoftwaretransparent.vcxproj @@ -0,0 +1,209 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {D91C45E2-274E-4C0F-89C7-9986F9A7E85A} + testsoftwaretransparent + 10.0 + + + + Application + $(DefaultPlatformToolset) + + + Application + $(DefaultPlatformToolset) + + + Application + $(DefaultPlatformToolset) + + + Application + $(DefaultPlatformToolset) + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/testsoftwaretransparent.tlb + + + %(AdditionalOptions) /utf-8 + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + Windows + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + .\Release/testsoftwaretransparent.tlb + + + %(AdditionalOptions) /utf-8 + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + Windows + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Debug/testsoftwaretransparent.tlb + + + %(AdditionalOptions) /utf-8 + Disabled + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + OldStyle + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + Windows + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + X64 + .\Debug/testsoftwaretransparent.tlb + + + %(AdditionalOptions) /utf-8 + Disabled + $(SolutionDir)/../include;%(AdditionalIncludeDirectories) + %(AdditionalUsingDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + OldStyle + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + Windows + + + + + $(TreatWarningsAsError) + + + + + {81ce8daf-ebb2-4761-8e45-b71abcca8c68} + false + false + true + + + {da956fd3-e143-46f2-9fe5-c77bebc56b1a} + false + false + true + + + + + + + + + \ No newline at end of file diff --git a/VisualC/tests/testsprite/testsprite.vcxproj b/VisualC/tests/testsprite/testsprite.vcxproj index 46473ccc9f..5baba6e47c 100644 --- a/VisualC/tests/testsprite/testsprite.vcxproj +++ b/VisualC/tests/testsprite/testsprite.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testsurround/testsurround.vcxproj b/VisualC/tests/testsurround/testsurround.vcxproj index 13ded82fa4..9e1b9c4d96 100644 --- a/VisualC/tests/testsurround/testsurround.vcxproj +++ b/VisualC/tests/testsurround/testsurround.vcxproj @@ -189,7 +189,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testvulkan/testvulkan.vcxproj b/VisualC/tests/testvulkan/testvulkan.vcxproj index 0e6645b4dc..f906fd0f40 100644 --- a/VisualC/tests/testvulkan/testvulkan.vcxproj +++ b/VisualC/tests/testvulkan/testvulkan.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testwm/testwm.vcxproj b/VisualC/tests/testwm/testwm.vcxproj index 48786d3404..9b74b039e6 100644 --- a/VisualC/tests/testwm/testwm.vcxproj +++ b/VisualC/tests/testwm/testwm.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/VisualC/tests/testyuv/testyuv.vcxproj b/VisualC/tests/testyuv/testyuv.vcxproj index 5adf0eee3f..5b7b9b4290 100644 --- a/VisualC/tests/testyuv/testyuv.vcxproj +++ b/VisualC/tests/testyuv/testyuv.vcxproj @@ -183,7 +183,6 @@ - %(AdditionalOptions) /utf-8 $(TreatWarningsAsError) diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index a6434e435a..074c5b69fc 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -229,6 +229,7 @@ A7D8B54523E2514300DCD162 /* SDL_hidapijoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C423E2513E00DCD162 /* SDL_hidapijoystick.c */; }; A7D8B54B23E2514300DCD162 /* SDL_hidapi_xboxone.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C523E2513E00DCD162 /* SDL_hidapi_xboxone.c */; }; A7D8B55123E2514300DCD162 /* SDL_hidapi_switch.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */; }; + A7D8B55123E2514300DCD163 /* SDL_hidapi_switch2.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */; }; A7D8B55723E2514300DCD162 /* SDL_hidapijoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A7C723E2513E00DCD162 /* SDL_hidapijoystick_c.h */; }; A7D8B55D23E2514300DCD162 /* SDL_hidapi_xbox360w.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C823E2513E00DCD162 /* SDL_hidapi_xbox360w.c */; }; A7D8B56323E2514300DCD162 /* SDL_hidapi_gamecube.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */; }; @@ -521,6 +522,8 @@ F3D60A8328C16A1900788A3A /* SDL_hidapi_wii.c in Sources */ = {isa = PBXBuildFile; fileRef = F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */; }; F3D8BDFC2D6D2C7000B22FA1 /* SDL_eventwatch_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3D8BDFB2D6D2C7000B22FA1 /* SDL_eventwatch_c.h */; }; F3D8BDFD2D6D2C7000B22FA1 /* SDL_eventwatch.c in Sources */ = {isa = PBXBuildFile; fileRef = F3D8BDFA2D6D2C7000B22FA1 /* SDL_eventwatch.c */; }; + F3DC38C92E5FC60300CD73DE /* SDL_libusb.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DC38C72E5FC60300CD73DE /* SDL_libusb.h */; }; + F3DC38CA2E5FC60300CD73DE /* SDL_libusb.c in Sources */ = {isa = PBXBuildFile; fileRef = F3DC38C82E5FC60300CD73DE /* SDL_libusb.c */; }; F3DDCC562AFD42B600B0842B /* SDL_clipboard_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC4D2AFD42B500B0842B /* SDL_clipboard_c.h */; }; F3DDCC5B2AFD42B600B0842B /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC522AFD42B600B0842B /* SDL_video_c.h */; }; F3DDCC5D2AFD42B600B0842B /* SDL_rect_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */; }; @@ -813,6 +816,7 @@ A7D8A7C423E2513E00DCD162 /* SDL_hidapijoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapijoystick.c; sourceTree = ""; }; A7D8A7C523E2513E00DCD162 /* SDL_hidapi_xboxone.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xboxone.c; sourceTree = ""; }; A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch.c; sourceTree = ""; }; + A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch2.c; sourceTree = ""; }; A7D8A7C723E2513E00DCD162 /* SDL_hidapijoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapijoystick_c.h; sourceTree = ""; }; A7D8A7C823E2513E00DCD162 /* SDL_hidapi_xbox360w.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xbox360w.c; sourceTree = ""; }; A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_gamecube.c; sourceTree = ""; }; @@ -1099,6 +1103,8 @@ F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_wii.c; sourceTree = ""; }; F3D8BDFA2D6D2C7000B22FA1 /* SDL_eventwatch.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_eventwatch.c; sourceTree = ""; }; F3D8BDFB2D6D2C7000B22FA1 /* SDL_eventwatch_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_eventwatch_c.h; sourceTree = ""; }; + F3DC38C72E5FC60300CD73DE /* SDL_libusb.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_libusb.h; sourceTree = ""; }; + F3DC38C82E5FC60300CD73DE /* SDL_libusb.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_libusb.c; sourceTree = ""; }; F3DDCC4D2AFD42B500B0842B /* SDL_clipboard_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_clipboard_c.h; sourceTree = ""; }; F3DDCC522AFD42B600B0842B /* SDL_video_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_video_c.h; sourceTree = ""; }; F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_rect_impl.h; sourceTree = ""; }; @@ -1412,6 +1418,8 @@ children = ( F3ADAB8C2576F08500A6B1D9 /* ios */, 5616CA48252BB285005D5928 /* macos */, + F3DC38C72E5FC60300CD73DE /* SDL_libusb.h */, + F3DC38C82E5FC60300CD73DE /* SDL_libusb.c */, 5616CA4A252BB2A6005D5928 /* SDL_sysurl.h */, 5616CA49252BB2A5005D5928 /* SDL_url.c */, ); @@ -1951,6 +1959,7 @@ F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */, A797456F2B2E9D39009D224A /* SDL_hidapi_steamdeck.c */, A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */, + A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */, F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */, A7D8A7C223E2513E00DCD162 /* SDL_hidapi_xbox360.c */, A7D8A7C823E2513E00DCD162 /* SDL_hidapi_xbox360w.c */, @@ -2747,6 +2756,7 @@ F3FA5A252B59ACE000FEAD97 /* yuv_rgb_common.h in Headers */, F3FA5A1D2B59ACE000FEAD97 /* yuv_rgb_internal.h in Headers */, F3D8BDFC2D6D2C7000B22FA1 /* SDL_eventwatch_c.h in Headers */, + F3DC38C92E5FC60300CD73DE /* SDL_libusb.h in Headers */, F3FA5A242B59ACE000FEAD97 /* yuv_rgb_lsx.h in Headers */, F3FA5A1E2B59ACE000FEAD97 /* yuv_rgb_lsx_func.h in Headers */, F3FA5A1F2B59ACE000FEAD97 /* yuv_rgb_sse.h in Headers */, @@ -2866,7 +2876,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Build an xcframework with both device and simulator files for all platforms.\n# Adapted from an answer in\n# https://developer.apple.com/forums/thread/666335?answerId=685927022#685927022\n\nif [ \"$XCODE_VERSION_ACTUAL\" -lt 1100 ]\nthen\n echo \"error: Building an xcframework requires Xcode 11 minimum.\"\n exit 1\nfi\n\nFRAMEWORK_NAME=\"SDL3\"\nPROJECT_NAME=\"SDL\"\nSCHEME=\"SDL3\"\n\nMACOS_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-macosx.xcarchive\"\nIOS_SIMULATOR_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphonesimulator.xcarchive\"\nIOS_DEVICE_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphoneos.xcarchive\"\nTVOS_SIMULATOR_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-appletvsimulator.xcarchive\"\nTVOS_DEVICE_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-appletvos.xcarchive\"\n\nOUTPUT_DIR=\"./build/\"\n\n# macOS\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${MACOS_ARCHIVE_PATH} \\\n -destination 'generic/platform=macOS,name=Any Mac' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n \n# iOS simulator\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${IOS_SIMULATOR_ARCHIVE_PATH} \\\n -destination 'generic/platform=iOS Simulator' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# iOS device\nxcodebuild archive \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${IOS_DEVICE_ARCHIVE_PATH} \\\n -destination 'generic/platform=iOS' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# tvOS simulator\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${TVOS_SIMULATOR_ARCHIVE_PATH} \\\n -destination 'generic/platform=tvOS Simulator' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# tvOS device\nxcodebuild archive \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${TVOS_DEVICE_ARCHIVE_PATH} \\\n -destination 'generic/platform=tvOS' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# Clean-up any existing instance of this xcframework from the Products directory\nrm -rf \"${OUTPUT_DIR}${FRAMEWORK_NAME}.xcframework\"\n\n# Create final xcframework\nxcodebuild -create-xcframework \\\n -framework \"${MACOS_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${IOS_DEVICE_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${IOS_SIMULATOR_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${TVOS_DEVICE_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${TVOS_SIMULATOR_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -output ${OUTPUT_DIR}/${FRAMEWORK_NAME}.xcframework\n\n# Ensure git doesn't pick up on our Products folder. \nrm -rf ${OUTPUT_DIR}/.gitignore\necho \"*\" >> ${OUTPUT_DIR}/.gitignore\n"; + shellScript = "# Build an xcframework with both device and simulator files for all platforms.\n# Adapted from an answer in\n# https://developer.apple.com/forums/thread/666335?answerId=685927022#685927022\n\nif [ \"$XCODE_VERSION_ACTUAL\" -lt 1100 ]\nthen\n echo \"error: Building an xcframework requires Xcode 11 minimum.\"\n exit 1\nfi\n\nFRAMEWORK_NAME=\"SDL3\"\nPROJECT_NAME=\"SDL\"\nSCHEME=\"SDL3\"\n\nMACOS_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-macosx.xcarchive\"\nIOS_SIMULATOR_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphonesimulator.xcarchive\"\nIOS_DEVICE_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphoneos.xcarchive\"\nTVOS_SIMULATOR_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-appletvsimulator.xcarchive\"\nTVOS_DEVICE_ARCHIVE_PATH=\"${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-appletvos.xcarchive\"\n\nOUTPUT_DIR=\"./build/\"\n\n# macOS\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${MACOS_ARCHIVE_PATH} \\\n -destination 'generic/platform=macOS,name=Any Mac' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n \n# iOS simulator\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${IOS_SIMULATOR_ARCHIVE_PATH} \\\n -destination 'generic/platform=iOS Simulator' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# iOS device\nxcodebuild archive \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${IOS_DEVICE_ARCHIVE_PATH} \\\n -destination 'generic/platform=iOS' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# tvOS simulator\nxcodebuild archive \\\n ONLY_ACTIVE_ARCH=NO \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${TVOS_SIMULATOR_ARCHIVE_PATH} \\\n -destination 'generic/platform=tvOS Simulator' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# tvOS device\nxcodebuild archive \\\n -scheme \"${SCHEME}\" \\\n -project \"${PROJECT_NAME}.xcodeproj\" \\\n -archivePath ${TVOS_DEVICE_ARCHIVE_PATH} \\\n -destination 'generic/platform=tvOS' \\\n BUILD_LIBRARY_FOR_DISTRIBUTION=YES \\\n SKIP_INSTALL=NO || exit $?\n\n# Clean-up any existing instance of this xcframework from the Products directory\nrm -rf \"${OUTPUT_DIR}${FRAMEWORK_NAME}.xcframework\"\n\n# Create final xcframework\nxcodebuild -create-xcframework \\\n -framework \"${MACOS_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -debug-symbols \"${MACOS_ARCHIVE_PATH}\"/dSYMs/$FRAMEWORK_NAME.framework.dSYM \\\n -framework \"${IOS_DEVICE_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -debug-symbols \"${IOS_DEVICE_ARCHIVE_PATH}\"/dSYMs/$FRAMEWORK_NAME.framework.dSYM \\\n -framework \"${IOS_SIMULATOR_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -framework \"${TVOS_DEVICE_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -debug-symbols \"${TVOS_DEVICE_ARCHIVE_PATH}\"/dSYMs/$FRAMEWORK_NAME.framework.dSYM \\\n -framework \"${TVOS_SIMULATOR_ARCHIVE_PATH}\"/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \\\n -output ${OUTPUT_DIR}/${FRAMEWORK_NAME}.xcframework\n\n# Ensure git doesn't pick up on our Products folder. \nrm -rf ${OUTPUT_DIR}/.gitignore\necho \"*\" >> ${OUTPUT_DIR}/.gitignore\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -3039,6 +3049,7 @@ F3FA5A222B59ACE000FEAD97 /* yuv_rgb_sse.c in Sources */, F3C2CB232C5DDDB2004D7998 /* SDL_categories.c in Sources */, A7D8B55123E2514300DCD162 /* SDL_hidapi_switch.c in Sources */, + A7D8B55123E2514300DCD163 /* SDL_hidapi_switch2.c in Sources */, A7D8B96223E2514400DCD162 /* SDL_strtokr.c in Sources */, A7D8BB7523E2514500DCD162 /* SDL_clipboardevents.c in Sources */, E4F798202AD8D87F00669F54 /* SDL_video_unsupported.c in Sources */, @@ -3097,6 +3108,7 @@ 0000481D255AF155B42C0000 /* SDL_sysfsops.c in Sources */, 0000494CC93F3E624D3C0000 /* SDL_systime.c in Sources */, 000095FA1BDE436CF3AF0000 /* SDL_time.c in Sources */, + F3DC38CA2E5FC60300CD73DE /* SDL_libusb.c in Sources */, 0000140640E77F73F1DF0000 /* SDL_dialog_utils.c in Sources */, 0000D5B526B85DE7AB1C0000 /* SDL_cocoapen.m in Sources */, 6312C66D2B42341400A7BB00 /* SDL_murmur3.c in Sources */, @@ -3178,6 +3190,8 @@ baseConfigurationReference = F3F7BE3B2CBD79D200C984AF /* config.xcconfig */; buildSettings = { CLANG_LINK_OBJC_RUNTIME = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; OTHER_LDFLAGS = "-liconv"; SUPPORTS_MACCATALYST = YES; }; @@ -3240,6 +3254,8 @@ baseConfigurationReference = F3F7BE3B2CBD79D200C984AF /* config.xcconfig */; buildSettings = { CLANG_LINK_OBJC_RUNTIME = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; OTHER_LDFLAGS = "-liconv"; SUPPORTS_MACCATALYST = YES; }; diff --git a/android-project/app/src/main/AndroidManifest.xml b/android-project/app/src/main/AndroidManifest.xml index f3a7cd5ce6..ab43f76aef 100644 --- a/android-project/app/src/main/AndroidManifest.xml +++ b/android-project/app/src/main/AndroidManifest.xml @@ -71,6 +71,7 @@ android:icon="@mipmap/ic_launcher" android:allowBackup="true" android:theme="@style/AppTheme" + android:enableOnBackInvokedCallback="false" android:hardwareAccelerated="true" > \n\n"; + print $fh "# $envvartitle\n\n"; + + if (defined $envvardesc) { + my $desc = "$envvardesc"; + $desc =~ s/\\n/\n/g; # replace "\n" strings with actual newlines. + print $fh "$desc\n\n"; + } + + print $fh "## Environment Variable List\n\n"; + + foreach (sort keys %headersyms) { + my $sym = $_; + next if $headersymstype{$sym} != 2; # not a #define? skip it. + my $hint = "$_"; + next if not $hint =~ s/$envvarsymregex/$replace/ee; + + my $brief = $$briefsref{$sym}; + if (not defined $brief) { + $brief = ''; + } else { + $brief = "$brief"; + chomp($brief); + my $thiswikitype = defined $wikitypes{$sym} ? $wikitypes{$sym} : 'md'; # default to MarkDown for new stuff. + $brief = ": " . dewikify($thiswikitype, $brief); + } + print $fh "- [$hint]($sym)$brief\n"; + } + + print $fh "\n"; + + close($fh); + + rename($tmppath, $path) or die("Can't rename '$tmppath' to '$path': $!\n"); +} + + + + my $incpath = "$srcpath"; $incpath .= "/$incsubdir" if $incsubdir ne ''; @@ -2733,6 +2793,11 @@ __EOF__ generate_quickref(\%briefs, "$wikipath/QuickReference.md", 0); generate_quickref(\%briefs, "$wikipath/QuickReferenceNoUnicode.md", 1); } + + if ($envvarenabled and defined $envvarsymregex and defined $envvarsymreplace) { + generate_envvar_wiki_page(\%briefs, "$wikipath/EnvironmentVariables.md"); + } + } elsif ($copy_direction == -2) { # --copy-to-manpages # This only takes from the wiki data, since it has sections we omit from the headers, like code examples. diff --git a/cmake/sdlcommands.cmake b/cmake/sdlcommands.cmake index 1349326313..92670c191e 100644 --- a/cmake/sdlcommands.cmake +++ b/cmake/sdlcommands.cmake @@ -19,9 +19,15 @@ endfunction() # Use sdl_glob_sources to add glob sources to SDL3-shared, to SDL3-static, or to both. function(sdl_glob_sources) cmake_parse_arguments(ARGS "" "" "SHARED;STATIC" ${ARGN}) - file(GLOB shared_sources ${ARGS_SHARED}) - file(GLOB static_sources ${ARGS_STATIC}) - file(GLOB both_sources ${ARGS_UNPARSED_ARGUMENTS}) + if(ARGS_SHARED) + file(GLOB shared_sources CONFIGURE_DEPENDS ${ARGS_SHARED}) + endif() + if(ARGS_STATIC) + file(GLOB static_sources CONFIGURE_DEPENDS ${ARGS_STATIC}) + endif() + if(ARGS_UNPARSED_ARGUMENTS) + file(GLOB both_sources CONFIGURE_DEPENDS ${ARGS_UNPARSED_ARGUMENTS}) + endif() if(TARGET SDL3-shared) target_sources(SDL3-shared PRIVATE ${shared_sources} ${both_sources}) endif() diff --git a/cmake/sdlcompilers.cmake b/cmake/sdlcompilers.cmake index c3d8c4702e..af80a8eb24 100644 --- a/cmake/sdlcompilers.cmake +++ b/cmake/sdlcompilers.cmake @@ -160,3 +160,63 @@ function(SDL_AddCommonCompilerFlags TARGET) endif() endif() endfunction() + +function(check_x86_source_compiles BODY VAR) + if(ARGN) + message(FATAL_ERROR "Unknown arguments: ${ARGN}") + endif() + if(APPLE_MULTIARCH AND (SDL_CPU_X86 OR SDL_CPU_X64)) + set(test_conditional 1) + else() + set(test_conditional 0) + endif() + check_c_source_compiles(" + #if ${test_conditional} + # if defined(__i386__) || defined(__x86_64__) + # define test_enabled 1 + # else + # define test_enabled 0 /* feign success in Apple multi-arch configs */ + # endif + #else /* test normally */ + # define test_enabled 1 + #endif + #if test_enabled + ${BODY} + #else + int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + return 0; + } + #endif" ${VAR}) +endfunction() + +function(check_arm_source_compiles BODY VAR) + if(ARGN) + message(FATAL_ERROR "Unknown arguments: ${ARGN}") + endif() + if(APPLE_MULTIARCH AND (SDL_CPU_ARM32 OR SDL_CPU_ARM64)) + set(test_conditional 1) + else() + set(test_conditional 0) + endif() + check_c_source_compiles(" + #if ${test_conditional} + # if defined(__arm__) || defined(__aarch64__) + # define test_enabled 1 + # else + # define test_enabled 0 /* feign success in Apple multi-arch configs */ + # endif + #else /* test normally */ + # define test_enabled 1 + #endif + #if test_enabled + ${BODY} + #else + int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + return 0; + } + #endif" ${VAR}) +endfunction() diff --git a/docs/README-emscripten.md b/docs/README-emscripten.md index 54e7d2de50..aa6a1e3f30 100644 --- a/docs/README-emscripten.md +++ b/docs/README-emscripten.md @@ -239,7 +239,7 @@ If you want to build with thread support, something like this works: ```bash mkdir build cd build -emcmake cmake -DSDL_THREADS=ON .. +emcmake cmake -DSDL_PTHREADS=ON .. # you can also do `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. emmake make -j4 ``` diff --git a/docs/README-ios.md b/docs/README-ios.md index 166d18261f..74f4ca6982 100644 --- a/docs/README-ios.md +++ b/docs/README-ios.md @@ -12,9 +12,44 @@ Instructions: 2. Select your desired target, and hit build. -Using the Simple DirectMedia Layer for iOS +Using the Simple DirectMedia Layer for iOS with the SDL3 xcFramework ============================================================================== +The recommended way to use SDL for iOS is by including the SDL3.xcframework which is now a build target of the SDL.xcodeproj file. An xcframework is a new (Xcode 11) uber-framework which can handle any combination of processor type and target OS platform. +You can either build the SDL3.xcframework yourself or download the latest release disk image asset (*.dmg). + +In the past, iOS devices were always an ARM variant processor, and the simulator was always i386 or x86_64, and thus libraries could be combined into a single framework for both simulator and device. With the introduction of the Apple Silicon ARM-based machines, regular frameworks would collide as CPU type was no longer sufficient to differentiate the platform. So Apple created the new xcframework library package. + +The xcframework target builds into a Products directory alongside the SDL.xcodeproj file, as SDL3.xcframework. This can be brought in to any iOS project and will function properly for both simulator and device, no matter their CPUs. Note that Intel Macs cannot cross-compile for Apple Silicon Macs. If you need AS compatibility, perform this build on an Apple Silicon Mac. + +This target requires Xcode 11 or later. The target will simply fail to build if attempted on older Xcodes. + +In addition, on Apple platforms, main() cannot be in a dynamically loaded library. +However, unlike in SDL2, in SDL3 SDL_main is implemented inline in SDL_main.h, so you don't need to link against a static libSDL3main.lib, and you don't need to copy a .c file from the SDL3 source either. +This means that iOS apps which used the statically-linked libSDL3.lib and now link with the xcframwork can just `#include ` in the source file that contains their standard `int main(int argc, char *argv[])` function to get a header-only SDL_main implementation that calls the `SDL_RunApp()` with your standard main function. + +To use the SDL3.xcframework follow these steps: + +1. Run Xcode and create a new project using the iOS Game template, selecting the Objective C language and Metal game technology. +2. In the main view, delete all files except for Assets and LaunchScreen +3. Select the project in the main view, go to the "General" tab, scroll down to "Frameworks, Libraries, and Embedded Content", and drag and drop the SDL3.xcframework +4. Still in "Frameworks, Libraries, and Embedded Content", select "Embed & Sign" for the SDL3.xcframework. +5. Add the source files that you would normally have for an SDL program, making sure to have #include at the top of the file containing your main() function. +6. Add any assets that your application needs. +7. Enjoy! + +Using an xcFramework is similar to using a regular framework. However, issues have been seen with the build system not seeing the headers in the xcFramework. To remedy this, add the path to the xcFramework in your app's target ==> Build Settings ==> Framework Search Paths and mark it recursive (this is critical). Also critical is to remove "*.framework" from Build Settings ==> Sub-Directories to Exclude in Recursive Searches. Clean the build folder, and on your next build the build system should be able to see any of these in your code, as expected: + +#include "SDL3/SDL_main.h" +#include +#include + + +Using the Simple DirectMedia Layer for iOS by adding the SDL3 Xcode project +============================================================================== + +To maintain compatibility with older Xcode versions than version 11 you can add the SDL3 Xcode project file to your project: + 1. Run Xcode and create a new project using the iOS Game template, selecting the Objective C language and Metal game technology. 2. In the main view, delete all files except for Assets and LaunchScreen 3. Right click the project in the main view, select "Add Files...", and add the SDL project, Xcode/SDL/SDL.xcodeproj @@ -26,6 +61,18 @@ Using the Simple DirectMedia Layer for iOS 9. Add any assets that your application needs. 10. Enjoy! +Notes for distributing your iOS app on the AppStore when using the embedded SDL3 Xcode project: +Embedding the SDL3 Xcode project makes SDL3.framework a target of your app, so it will be included in the archive created during the "Archive" step required for App Store submission. As this prevents successful distribution, remove the framework via a script in the Build Phase after the "Embed & Sign" step. + +1. Select the project in the main view, go to the "Build Phases" tab, click on the big '+' in this tab and click "New Run Script Phase". +2. Scroll down to "Run Script" (after the "Embed SDL3 Framework") and enter the following script: +``` + if [ -d "$INSTALL_ROOT/Library" ]; then + echo "Removing SDL3.framework from INSTALL_ROOT for archiving" + rm -rf "$INSTALL_ROOT/Library" + fi +``` +3. Below the script entry uncheck the "Run Script:" options "For install builds only" and "Based on dependency analysis" TODO: Add information regarding App Store requirements such as icons, etc. @@ -154,26 +201,7 @@ More information on this subject is available here: http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html -Notes -- xcFramework -============================================================================== -The SDL.xcodeproj file now includes a target to build SDL3.xcframework. An xcframework is a new (Xcode 11) uber-framework which can handle any combination of processor type and target OS platform. - -In the past, iOS devices were always an ARM variant processor, and the simulator was always i386 or x86_64, and thus libraries could be combined into a single framework for both simulator and device. With the introduction of the Apple Silicon ARM-based machines, regular frameworks would collide as CPU type was no longer sufficient to differentiate the platform. So Apple created the new xcframework library package. - -The xcframework target builds into a Products directory alongside the SDL.xcodeproj file, as SDL3.xcframework. This can be brought in to any iOS project and will function properly for both simulator and device, no matter their CPUs. Note that Intel Macs cannot cross-compile for Apple Silicon Macs. If you need AS compatibility, perform this build on an Apple Silicon Mac. - -This target requires Xcode 11 or later. The target will simply fail to build if attempted on older Xcodes. - -In addition, on Apple platforms, main() cannot be in a dynamically loaded library. -However, unlike in SDL2, in SDL3 SDL_main is implemented inline in SDL_main.h, so you don't need to link against a static libSDL3main.lib, and you don't need to copy a .c file from the SDL3 source either. -This means that iOS apps which used the statically-linked libSDL3.lib and now link with the xcframwork can just `#include ` in the source file that contains their standard `int main(int argc, char *argv[])` function to get a header-only SDL_main implementation that calls the `SDL_RunApp()` with your standard main function. - -Using an xcFramework is similar to using a regular framework. However, issues have been seen with the build system not seeing the headers in the xcFramework. To remedy this, add the path to the xcFramework in your app's target ==> Build Settings ==> Framework Search Paths and mark it recursive (this is critical). Also critical is to remove "*.framework" from Build Settings ==> Sub-Directories to Exclude in Recursive Searches. Clean the build folder, and on your next build the build system should be able to see any of these in your code, as expected: - -#include "SDL_main.h" -#include -#include Notes -- iPhone SDL limitations diff --git a/docs/README-linux.md b/docs/README-linux.md index 42891def8a..2e08548df1 100644 --- a/docs/README-linux.md +++ b/docs/README-linux.md @@ -26,7 +26,7 @@ Ubuntu 22.04+ can also add `libpipewire-0.3-dev libwayland-dev libdecor-0-dev li Fedora 35, all available features enabled: sudo yum install gcc git-core make cmake \ - alsa-lib-devel pulseaudio-libs-devel nas-devel pipewire-devel \ + alsa-lib-devel pulseaudio-libs-devel pipewire-devel \ libX11-devel libXext-devel libXrandr-devel libXcursor-devel libXfixes-devel \ libXi-devel libXScrnSaver-devel dbus-devel ibus-devel \ systemd-devel mesa-libGL-devel libxkbcommon-devel mesa-libGLES-devel \ @@ -43,7 +43,8 @@ NOTES: openSUSE Tumbleweed: sudo zypper in libunwind-devel libusb-1_0-devel Mesa-libGL-devel libxkbcommon-devel libdrm-devel \ - libgbm-devel pipewire-devel libpulse-devel sndio-devel Mesa-libEGL-devel + libgbm-devel pipewire-devel libpulse-devel sndio-devel Mesa-libEGL-devel alsa-devel xwayland-devel \ + wayland-devel wayland-protocols-devel Arch: diff --git a/docs/README-wayland.md b/docs/README-wayland.md index a3cd06f78a..720807032c 100644 --- a/docs/README-wayland.md +++ b/docs/README-wayland.md @@ -47,8 +47,15 @@ encounter limitations or behavior that is different from other windowing systems ### Warping the mouse cursor to or from a point outside the window doesn't work -- The cursor can be warped only within the window with mouse focus, provided that the `zwp_pointer_confinement_v1` - protocol is supported by the compositor. +- Warping the cursor on Wayland requires that either the `wp_pointer_warp_v1` or `zwp_pointer_confinement_v1` protocol + is supported by the compositor. Compositors typically restrict pointer warps to be within the window that currently + has mouse focus. + +### Minimize/Restored window events are not sent, and the ```SDL_WINDOW_MINIMIZED``` flag is not set. + +- Wayland windows do not currently report the minimized state, aside from when it is activated programmatically via + ```SDL_MinimizeWindow()```. Minimizing a window from the window controls or a desktop shortcut will not send a + minimized event or flag the window as being minimized. ### The application icon can't be set via ```SDL_SetWindowIcon()``` diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1c5a40e69e..fa4ad4dc29 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -16,13 +16,6 @@ else() endif() set(HAVE_EXAMPLES_LINK_SHARED "${SDL_EXAMPLES_LINK_SHARED}" PARENT_SCOPE) -# CMake incorrectly detects opengl32.lib being present on MSVC ARM64 -if(NOT (MSVC AND SDL_CPU_ARM64)) - # Prefer GLVND, if present - set(OpenGL_GL_PREFERENCE GLVND) - find_package(OpenGL) -endif() - set(SDL_EXAMPLE_EXECUTABLES) if(CMAKE_RUNTIME_OUTPUT_DIRECTORY) @@ -64,17 +57,12 @@ macro(add_sdl_example_executable TARGET) if(NOT AST_SOURCES) message(FATAL_ERROR "add_sdl_example_executable needs at least one source") endif() - set(EXTRA_SOURCES "") - if(AST_DATAFILES) - list(APPEND EXTRA_SOURCES ${DATAFILES}) - endif() if(ANDROID) - add_library(${TARGET} SHARED ${AST_SOURCES} ${EXTRA_SOURCES}) + add_library(${TARGET} SHARED ${AST_SOURCES} ${AST_DATAFILES}) else() - add_executable(${TARGET} ${AST_SOURCES} ${EXTRA_SOURCES}) + add_executable(${TARGET} ${AST_SOURCES} ${AST_DATAFILES}) endif() SDL_AddCommonCompilerFlags(${TARGET}) - target_include_directories(${TARGET} PRIVATE "${SDL3_SOURCE_DIR}/src/video/khronos") target_link_libraries(${TARGET} PRIVATE SDL3::${sdl_name_component}) list(APPEND SDL_EXAMPLE_EXECUTABLES ${TARGET}) @@ -85,13 +73,9 @@ macro(add_sdl_example_executable TARGET) COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different ${AST_DATAFILES} $/sdl-${TARGET} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" ) - else() + elseif(NOT APPLE AND NOT N3DS) add_dependencies(${TARGET} copy-sdl-example-resources) endif() - if(APPLE) - # Make sure resource files get installed into macOS/iOS .app bundles. - set_target_properties(${TARGET} PROPERTIES RESOURCE "${AST_DATAFILES}") - endif() if(EMSCRIPTEN) foreach(res IN LISTS AST_DATAFILES) get_filename_component(res_name "${res}" NAME) @@ -101,28 +85,45 @@ macro(add_sdl_example_executable TARGET) set_property(TARGET ${TARGET} APPEND PROPERTY ADDITIONAL_CLEAN_FILES "$/$$/>") endif() - if(WINDOWS) + if(APPLE) + # Set Apple App ID / Bundle ID. This is needed to launch apps on some Apple + # platforms (iOS, for example). + set_target_properties(${TARGET} PROPERTIES + RESOURCES "${AST_DATAFILES}" + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_GUI_IDENTIFIER "org.libsdl.${TARGET}" + MACOSX_BUNDLE_BUNDLE_VERSION "${SDL3_VERSION}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${SDL3_VERSION}" + ) + set_property(SOURCE ${AST_DATAFILES} PROPERTY MACOSX_PACKAGE_LOCATION "Resources") + elseif(WINDOWS) # CET support was added in VS 16.7 if(MSVC_VERSION GREATER 1926 AND CMAKE_GENERATOR_PLATFORM MATCHES "Win32|x64") set_property(TARGET ${TARGET} APPEND_STRING PROPERTY LINK_FLAGS " -CETCOMPAT") endif() - elseif(PSP) - target_link_libraries(${TARGET} PRIVATE GL) elseif(EMSCRIPTEN) set_property(TARGET ${TARGET} PROPERTY SUFFIX ".html") target_link_options(${TARGET} PRIVATE -sALLOW_MEMORY_GROWTH=1) + elseif(N3DS) + set(ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/romfs/${TARGET}") + file(MAKE_DIRECTORY "${ROMFS_DIR}") + file(COPY ${AST_DATAFILES} DESTINATION "${ROMFS_DIR}") + ctr_generate_smdh("${TARGET}.smdh" + NAME "SDL-${TARGET}" + DESCRIPTION "SDL3 example application" + AUTHOR "SDL3 Contributors" + ICON "${CMAKE_CURRENT_SOURCE_DIR}/../test/n3ds/logo48x48.png" + ) + ctr_create_3dsx( + ${TARGET} + ROMFS "${ROMFS_DIR}" + SMDH "${TARGET}.smdh" + ) elseif(NGAGE) string(MD5 TARGET_MD5 "${TARGET}") string(SUBSTRING "${TARGET_MD5}" 0 8 TARGET_MD5_8) target_link_options(${TARGET} PRIVATE "SHELL:-s UID3=0x${TARGET_MD5_8}") endif() - - if(OPENGL_FOUND) - target_compile_definitions(${TARGET} PRIVATE HAVE_OPENGL) - endif() - - # FIXME: only add "${SDL3_BINARY_DIR}/include-config-$>" + include paths of external dependencies - target_include_directories(${TARGET} PRIVATE "$") endmacro() add_sdl_example_executable(renderer-clear SOURCES renderer/01-clear/clear.c) @@ -181,28 +182,6 @@ if(PSP) endforeach() endif() -if(N3DS) - set(ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/romfs") - file(MAKE_DIRECTORY "${ROMFS_DIR}") - file(COPY ${RESOURCE_FILES} DESTINATION "${ROMFS_DIR}") - - foreach(APP ${SDL_EXAMPLE_EXECUTABLES}) - get_target_property(TARGET_BINARY_DIR ${APP} BINARY_DIR) - set(SMDH_FILE "${TARGET_BINARY_DIR}/${APP}.smdh") - ctr_generate_smdh("${SMDH_FILE}" - NAME "SDL-${APP}" - DESCRIPTION "SDL3 Test suite" - AUTHOR "SDL3 Contributors" - ICON "${CMAKE_CURRENT_SOURCE_DIR}/../test/n3ds/logo48x48.png" - ) - ctr_create_3dsx( - ${APP} - ROMFS "${ROMFS_DIR}" - SMDH "${SMDH_FILE}" - ) - endforeach() -endif() - if(RISCOS) set(SDL_EXAMPLE_EXECUTABLES_AIF) foreach(APP ${SDL_EXAMPLE_EXECUTABLES}) @@ -217,18 +196,6 @@ if(RISCOS) endforeach() endif() -# Set Apple App ID / Bundle ID. This is needed to launch apps on some Apple -# platforms (iOS, for example). -if(APPLE) - foreach(CURRENT_TARGET ${SDL_EXAMPLE_EXECUTABLES}) - set_target_properties("${CURRENT_TARGET}" PROPERTIES - MACOSX_BUNDLE_GUI_IDENTIFIER "org.libsdl.${CURRENT_TARGET}" - MACOSX_BUNDLE_BUNDLE_VERSION "${SDL3_VERSION}" - MACOSX_BUNDLE_SHORT_VERSION_STRING "${SDL3_VERSION}" - ) - endforeach() -endif() - if(SDL_INSTALL_EXAMPLES) if(RISCOS) install( diff --git a/examples/demo/01-snake/snake.c b/examples/demo/01-snake/snake.c index d740f2c6e0..249cf300ca 100644 --- a/examples/demo/01-snake/snake.c +++ b/examples/demo/01-snake/snake.c @@ -18,7 +18,8 @@ #define SNAKE_GAME_HEIGHT 18U #define SNAKE_MATRIX_SIZE (SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT) -#define THREE_BITS 0x7U /* ~CHAR_MAX >> (CHAR_BIT - SNAKE_CELL_MAX_BITS) */ +#define SNAKE_CELL_MAX_BITS 3U /* floor(log2(SNAKE_CELL_FOOD)) + 1 */ +#define SNAKE_CELL_SET_BITS (~(~0u << SNAKE_CELL_MAX_BITS)) #define SHIFT(x, y) (((x) + ((y) * SNAKE_GAME_WIDTH)) * SNAKE_CELL_MAX_BITS) static SDL_Joystick *joystick = NULL; @@ -33,8 +34,6 @@ typedef enum SNAKE_CELL_FOOD = 5U } SnakeCell; -#define SNAKE_CELL_MAX_BITS 3U /* floor(log2(SNAKE_CELL_FOOD)) + 1 */ - typedef enum { SNAKE_DIR_RIGHT, @@ -68,7 +67,7 @@ SnakeCell snake_cell_at(const SnakeContext *ctx, char x, char y) const int shift = SHIFT(x, y); unsigned short range; SDL_memcpy(&range, ctx->cells + (shift / 8), sizeof(range)); - return (SnakeCell)((range >> (shift % 8)) & THREE_BITS); + return (SnakeCell)((range >> (shift % 8)) & SNAKE_CELL_SET_BITS); } static void set_rect_xy_(SDL_FRect *r, short x, short y) @@ -84,8 +83,8 @@ static void put_cell_at_(SnakeContext *ctx, char x, char y, SnakeCell ct) unsigned char *const pos = ctx->cells + (shift / 8); unsigned short range; SDL_memcpy(&range, pos, sizeof(range)); - range &= ~(THREE_BITS << adjust); /* clear bits */ - range |= (ct & THREE_BITS) << adjust; + range &= ~(SNAKE_CELL_SET_BITS << adjust); /* clear bits */ + range |= (ct & SNAKE_CELL_SET_BITS) << adjust; SDL_memcpy(pos, &range, sizeof(range)); } diff --git a/include/SDL3/SDL_atomic.h b/include/SDL3/SDL_atomic.h index 78b5e0fa5b..bfcf81ee06 100644 --- a/include/SDL3/SDL_atomic.h +++ b/include/SDL3/SDL_atomic.h @@ -596,6 +596,24 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_SetAtomicU32(SDL_AtomicU32 *a, Uint32 v); */ extern SDL_DECLSPEC Uint32 SDLCALL SDL_GetAtomicU32(SDL_AtomicU32 *a); +/** + * Add to an atomic variable. + * + * This function also acts as a full memory barrier. + * + * ***Note: If you don't know what this function is for, you shouldn't use + * it!*** + * + * \param a a pointer to an SDL_AtomicU32 variable to be modified. + * \param v the desired value to add or subtract. + * \returns the previous value of the atomic variable. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC Uint32 SDLCALL SDL_AddAtomicU32(SDL_AtomicU32 *a, int v); + /** * Set a pointer to a new value if it is currently an old value. * diff --git a/include/SDL3/SDL_camera.h b/include/SDL3/SDL_camera.h index c0e2ee0fed..6dc79d3783 100644 --- a/include/SDL3/SDL_camera.h +++ b/include/SDL3/SDL_camera.h @@ -136,6 +136,20 @@ typedef enum SDL_CameraPosition SDL_CAMERA_POSITION_BACK_FACING } SDL_CameraPosition; +/** + * The current state of a request for camera access. + * + * \since This enum is available since SDL 3.4.0. + * + * \sa SDL_GetCameraPermissionState + */ +typedef enum SDL_CameraPermissionState +{ + SDL_CAMERA_PERMISSION_STATE_DENIED = -1, + SDL_CAMERA_PERMISSION_STATE_PENDING, + SDL_CAMERA_PERMISSION_STATE_APPROVED, +} SDL_CameraPermissionState; + /** * Use this function to get the number of built-in camera drivers. @@ -368,7 +382,7 @@ extern SDL_DECLSPEC SDL_Camera * SDLCALL SDL_OpenCamera(SDL_CameraID instance_id * \sa SDL_OpenCamera * \sa SDL_CloseCamera */ -extern SDL_DECLSPEC int SDLCALL SDL_GetCameraPermissionState(SDL_Camera *camera); +extern SDL_DECLSPEC SDL_CameraPermissionState SDLCALL SDL_GetCameraPermissionState(SDL_Camera *camera); /** * Get the instance ID of an opened camera. diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 85e63baf6d..2af19fa2e9 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -235,7 +235,8 @@ * SDL driver name: "direct3d12" * * Supported on Windows 10 or newer, Xbox One (GDK), and Xbox Series X|S - * (GDK). Requires a GPU that supports DirectX 12 Feature Level 11_1. + * (GDK). Requires a GPU that supports DirectX 12 Feature Level 11_0 and + * Resource Binding Tier 2 or above. * * ### Metal * @@ -1995,6 +1996,7 @@ typedef struct SDL_GPUComputePipelineCreateInfo * \since This struct is available since SDL 3.2.0. * * \sa SDL_BeginGPURenderPass + * \sa SDL_FColor */ typedef struct SDL_GPUColorTargetInfo { @@ -2114,6 +2116,8 @@ typedef struct SDL_GPUBufferBinding * * \sa SDL_BindGPUVertexSamplers * \sa SDL_BindGPUFragmentSamplers + * \sa SDL_GPUTexture + * \sa SDL_GPUSampler */ typedef struct SDL_GPUTextureSamplerBinding { @@ -3755,6 +3759,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnmapGPUTransferBuffer( * \returns a copy pass handle. * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_EndGPUCopyPass */ extern SDL_DECLSPEC SDL_GPUCopyPass * SDLCALL SDL_BeginGPUCopyPass( SDL_GPUCommandBuffer *command_buffer); diff --git a/include/SDL3/SDL_hidapi.h b/include/SDL3/SDL_hidapi.h index 131b03723a..67e29c2a1d 100644 --- a/include/SDL3/SDL_hidapi.h +++ b/include/SDL3/SDL_hidapi.h @@ -55,6 +55,7 @@ #include #include +#include #include /* Set up for C function definitions, even when using C++ */ @@ -283,6 +284,24 @@ extern SDL_DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open(unsigned short vendor_ */ extern SDL_DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open_path(const char *path); +/** + * Get the properties associated with an SDL_hid_device. + * + * The following read-only properties are provided by SDL: + * + * - `SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER`: the libusb_device_handle + * associated with the device, if it was opened using libusb. + * + * \param dev a device handle returned from SDL_hid_open(). + * \returns a valid property ID on success or 0 on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_hid_get_properties(SDL_hid_device *dev); + +#define SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER "SDL.hidapi.libusb.device.handle" + /** * Write an Output report to a HID device. * diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 5e202827b3..b3103ae7f7 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -1823,6 +1823,23 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED "SDL_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED" +/** + * A variable controlling whether the HIDAPI driver for Nintendo Switch 2 + * controllers should be used. + * + * The variable can be set to the following values: + * + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH2 "SDL_JOYSTICK_HIDAPI_SWITCH2" + /** * A variable controlling whether Nintendo Switch Joy-Con controllers will be * in vertical mode when using the HIDAPI driver. diff --git a/include/SDL3/SDL_iostream.h b/include/SDL3/SDL_iostream.h index 2e6a7b52a9..0fb3271854 100644 --- a/include/SDL3/SDL_iostream.h +++ b/include/SDL3/SDL_iostream.h @@ -111,7 +111,7 @@ typedef struct SDL_IOStreamInterface /** * Read up to `size` bytes from the data stream to the area pointed - * at by `ptr`. + * at by `ptr`. `size` will always be > 0. * * On an incomplete read, you should set `*status` to a value from the * SDL_IOStatus enum. You do not have to explicitly set this on @@ -123,7 +123,7 @@ typedef struct SDL_IOStreamInterface /** * Write exactly `size` bytes from the area pointed at by `ptr` - * to data stream. + * to data stream. `size` will always be > 0. * * On an incomplete write, you should set `*status` to a value from the * SDL_IOStatus enum. You do not have to explicitly set this on @@ -260,7 +260,7 @@ typedef struct SDL_IOStream SDL_IOStream; * \returns a pointer to the SDL_IOStream structure that is created or NULL on * failure; call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * @@ -465,7 +465,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_OpenIO(const SDL_IOStreamInterfac * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -480,7 +480,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_CloseIO(SDL_IOStream *context); * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -500,7 +500,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetIOProperties(SDL_IOStream *c * \param context the SDL_IOStream to query. * \returns an SDL_IOStatus enum with the current state. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -514,7 +514,7 @@ extern SDL_DECLSPEC SDL_IOStatus SDLCALL SDL_GetIOStatus(SDL_IOStream *context); * negative error code on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -541,7 +541,7 @@ extern SDL_DECLSPEC Sint64 SDLCALL SDL_GetIOSize(SDL_IOStream *context); * \returns the final offset in the data stream after the seek or -1 on * failure; call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -561,7 +561,7 @@ extern SDL_DECLSPEC Sint64 SDLCALL SDL_SeekIO(SDL_IOStream *context, Sint64 offs * \returns the current offset in the stream, or -1 if the information can not * be determined. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -580,13 +580,17 @@ extern SDL_DECLSPEC Sint64 SDLCALL SDL_TellIO(SDL_IOStream *context); * the stream is not at EOF, SDL_GetIOStatus() will return a different error * value and SDL_GetError() will offer a human-readable message. * + * A request for zero bytes on a valid stream will return zero immediately + * without accessing the stream, so the stream status (EOF, err, etc) will not + * change. + * * \param context a pointer to an SDL_IOStream structure. * \param ptr a pointer to a buffer to read data into. * \param size the number of bytes to read from the data source. * \returns the number of bytes read, or 0 on end of file or other failure; * call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -609,13 +613,17 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_ReadIO(SDL_IOStream *context, void *ptr, * recoverable, such as a non-blocking write that can simply be retried later, * or a fatal error. * + * A request for zero bytes on a valid stream will return zero immediately + * without accessing the stream, so the stream status (EOF, err, etc) will not + * change. + * * \param context a pointer to an SDL_IOStream structure. * \param ptr a pointer to a buffer containing data to write. * \param size the number of bytes to write. * \returns the number of bytes written, which will be less than `size` on * failure; call SDL_GetError() for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -639,7 +647,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_WriteIO(SDL_IOStream *context, const void * \returns the number of bytes written or 0 on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -659,7 +667,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_IOprintf(SDL_IOStream *context, SDL_PRINT * \returns the number of bytes written or 0 on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -679,7 +687,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_IOvprintf(SDL_IOStream *context, SDL_PRIN * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -705,7 +713,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FlushIO(SDL_IOStream *context); * \returns the data or NULL on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -728,7 +736,7 @@ extern SDL_DECLSPEC void * SDLCALL SDL_LoadFile_IO(SDL_IOStream *src, size_t *da * \returns the data or NULL on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * @@ -749,7 +757,7 @@ extern SDL_DECLSPEC void * SDLCALL SDL_LoadFile(const char *file, size_t *datasi * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. * @@ -768,7 +776,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveFile_IO(SDL_IOStream *src, const void * * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.2.0. * @@ -797,7 +805,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveFile(const char *file, const void *data * \returns true on success or false on failure or EOF; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -816,7 +824,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU8(SDL_IOStream *src, Uint8 *value); * \returns true on success or false on failure; call SDL_GetError() for more * information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -839,7 +847,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS8(SDL_IOStream *src, Sint8 *value); * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -862,7 +870,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU16LE(SDL_IOStream *src, Uint16 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -885,7 +893,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS16LE(SDL_IOStream *src, Sint16 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -908,7 +916,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU16BE(SDL_IOStream *src, Uint16 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -931,7 +939,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS16BE(SDL_IOStream *src, Sint16 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -954,7 +962,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU32LE(SDL_IOStream *src, Uint32 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -977,7 +985,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS32LE(SDL_IOStream *src, Sint32 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1000,7 +1008,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU32BE(SDL_IOStream *src, Uint32 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1023,7 +1031,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS32BE(SDL_IOStream *src, Sint32 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1046,7 +1054,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU64LE(SDL_IOStream *src, Uint64 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1069,7 +1077,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS64LE(SDL_IOStream *src, Sint64 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1092,7 +1100,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU64BE(SDL_IOStream *src, Uint64 *value) * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1114,7 +1122,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS64BE(SDL_IOStream *src, Sint64 *value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1128,7 +1136,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU8(SDL_IOStream *dst, Uint8 value); * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1147,7 +1155,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS8(SDL_IOStream *dst, Sint8 value); * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1166,7 +1174,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU16LE(SDL_IOStream *dst, Uint16 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1184,7 +1192,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS16LE(SDL_IOStream *dst, Sint16 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1202,7 +1210,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU16BE(SDL_IOStream *dst, Uint16 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1221,7 +1229,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS16BE(SDL_IOStream *dst, Sint16 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1240,7 +1248,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU32LE(SDL_IOStream *dst, Uint32 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1258,7 +1266,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS32LE(SDL_IOStream *dst, Sint32 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1276,7 +1284,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU32BE(SDL_IOStream *dst, Uint32 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1295,7 +1303,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS32BE(SDL_IOStream *dst, Sint32 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1314,7 +1322,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU64LE(SDL_IOStream *dst, Uint64 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1332,7 +1340,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteS64LE(SDL_IOStream *dst, Sint64 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ @@ -1350,7 +1358,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WriteU64BE(SDL_IOStream *dst, Uint64 value) * \returns true on successful write or false on failure; call SDL_GetError() * for more information. * - * \threadsafety This function is not thread safe. + * \threadsafety Do not use the same SDL_IOStream from two threads at once. * * \since This function is available since SDL 3.2.0. */ diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h index 1278b3788f..411d64cb2e 100644 --- a/include/SDL3/SDL_main.h +++ b/include/SDL3/SDL_main.h @@ -615,11 +615,12 @@ extern SDL_DECLSPEC int SDLCALL SDL_EnterAppMainCallbacks(int argc, char *argv[] * Most applications do not need to, and should not, call this directly; SDL * will call it when initializing the video subsystem. * + * If `name` is NULL, SDL currently uses `(CS_BYTEALIGNCLIENT | CS_OWNDC)` for + * the style, regardless of what is specified here. + * * \param name the window class name, in UTF-8 encoding. If NULL, SDL * currently uses "SDL_app" but this isn't guaranteed. - * \param style the value to use in WNDCLASSEX::style. If `name` is NULL, SDL - * currently uses `(CS_BYTEALIGNCLIENT \| CS_OWNDC)` regardless - * of what is specified here. + * \param style the value to use in WNDCLASSEX::style. * \param hInst the HINSTANCE to use in WNDCLASSEX::hInstance. If zero, SDL * will use `GetModuleHandle(NULL)` instead. * \returns true on success or false on failure; call SDL_GetError() for more diff --git a/include/SDL3/SDL_power.h b/include/SDL3/SDL_power.h index 694fb0924d..bc2d9d53ef 100644 --- a/include/SDL3/SDL_power.h +++ b/include/SDL3/SDL_power.h @@ -88,8 +88,8 @@ typedef enum SDL_PowerState * can't determine a value or there is no battery. * \param percent a pointer filled in with the percentage of battery life * left, between 0 and 100, or NULL to ignore. This will be - * filled in with -1 we can't determine a value or there is no - * battery. + * filled in with -1 when we can't determine a value or there + * is no battery. * \returns the current battery state or `SDL_POWERSTATE_ERROR` on failure; * call SDL_GetError() for more information. * diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h index bfb758235f..135fd42437 100644 --- a/include/SDL3/SDL_render.h +++ b/include/SDL3/SDL_render.h @@ -283,7 +283,7 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window *window * present synchronized with the refresh rate. This property can take any * value that is supported by SDL_SetRenderVSync() for the renderer. * - * With the SDL GPU renderer: + * With the SDL GPU renderer (since SDL 3.4.0): * * - `SDL_PROP_RENDERER_CREATE_GPU_SHADERS_SPIRV_BOOLEAN`: the app is able to * provide SPIR-V shaders to SDL_GPURenderState, optional. @@ -2720,7 +2720,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderVSync(SDL_Renderer *renderer, int * break. If the text goes out of the window, it's gone. * * For serious text rendering, there are several good options, such as - * SDL_ttf, stb_truetype, or other external libraries. + * [SDL_ttf](https://wiki.libsdl.org/SDL3_ttf/FrontPage) + * , stb_truetype, or other external libraries. * * On first use, this will create an internal texture for rendering glyphs. * This texture will live until the renderer is destroyed. @@ -2808,18 +2809,14 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetDefaultTextureScaleMode(SDL_Renderer *re extern SDL_DECLSPEC bool SDLCALL SDL_GetDefaultTextureScaleMode(SDL_Renderer *renderer, SDL_ScaleMode *scale_mode); /** - * GPU render state description. - * - * This structure should be initialized using SDL_INIT_INTERFACE(). + * A structure specifying the parameters of a GPU render state. * * \since This struct is available since SDL 3.4.0. * * \sa SDL_CreateGPURenderState */ -typedef struct SDL_GPURenderStateDesc +typedef struct SDL_GPURenderStateCreateInfo { - Uint32 version; /**< the version of this interface */ - SDL_GPUShader *fragment_shader; /**< The fragment shader to use when this render state is active */ Sint32 num_sampler_bindings; /**< The number of additional fragment samplers to bind when this render state is active */ @@ -2828,19 +2825,11 @@ typedef struct SDL_GPURenderStateDesc Sint32 num_storage_textures; /**< The number of storage textures to bind when this render state is active */ SDL_GPUTexture *const *storage_textures; /**< Storage textures to bind when this render state is active */ - Sint32 num_storage_buffers; /**< The number of storage buffers to bind when this render state is active */ + Sint32 num_storage_buffers; /**< The number of storage buffers to bind when this render state is active */ SDL_GPUBuffer *const *storage_buffers; /**< Storage buffers to bind when this render state is active */ -} SDL_GPURenderStateDesc; -/* Check the size of SDL_GPURenderStateDesc - * - * If this assert fails, either the compiler is padding to an unexpected size, - * or the interface has been updated and this should be updated to match and - * the code using this interface should be updated to handle the old version. - */ -SDL_COMPILE_TIME_ASSERT(SDL_GPURenderStateDesc_SIZE, - (sizeof(void *) == 4 && sizeof(SDL_GPURenderStateDesc) == 32) || - (sizeof(void *) == 8 && sizeof(SDL_GPURenderStateDesc) == 64)); + SDL_PropertiesID props; /**< A properties ID for extensions. Should be 0 if no extensions are needed. */ +} SDL_GPURenderStateCreateInfo; /** * A custom GPU render state. @@ -2858,8 +2847,7 @@ typedef struct SDL_GPURenderState SDL_GPURenderState; * Create custom GPU render state. * * \param renderer the renderer to use. - * \param desc GPU render state description, initialized using - * SDL_INIT_INTERFACE(). + * \param createinfo a struct describing the GPU render state to create. * \returns a custom GPU render state or NULL on failure; call SDL_GetError() * for more information. * @@ -2872,7 +2860,7 @@ typedef struct SDL_GPURenderState SDL_GPURenderState; * \sa SDL_SetRenderGPUState * \sa SDL_DestroyGPURenderState */ -extern SDL_DECLSPEC SDL_GPURenderState * SDLCALL SDL_CreateGPURenderState(SDL_Renderer *renderer, SDL_GPURenderStateDesc *desc); +extern SDL_DECLSPEC SDL_GPURenderState * SDLCALL SDL_CreateGPURenderState(SDL_Renderer *renderer, SDL_GPURenderStateCreateInfo *createinfo); /** * Set fragment shader uniform variables in a custom GPU render state. diff --git a/include/SDL3/SDL_sensor.h b/include/SDL3/SDL_sensor.h index b220f0538c..43366f1357 100644 --- a/include/SDL3/SDL_sensor.h +++ b/include/SDL3/SDL_sensor.h @@ -138,7 +138,8 @@ typedef enum SDL_SensorType SDL_SENSOR_ACCEL_L, /**< Accelerometer for left Joy-Con controller and Wii nunchuk */ SDL_SENSOR_GYRO_L, /**< Gyroscope for left Joy-Con controller */ SDL_SENSOR_ACCEL_R, /**< Accelerometer for right Joy-Con controller */ - SDL_SENSOR_GYRO_R /**< Gyroscope for right Joy-Con controller */ + SDL_SENSOR_GYRO_R, /**< Gyroscope for right Joy-Con controller */ + SDL_SENSOR_COUNT } SDL_SensorType; diff --git a/include/SDL3/SDL_stdinc.h b/include/SDL3/SDL_stdinc.h index 21266e81e4..fa8c9a50e8 100644 --- a/include/SDL3/SDL_stdinc.h +++ b/include/SDL3/SDL_stdinc.h @@ -4722,7 +4722,7 @@ extern SDL_DECLSPEC float SDLCALL SDL_atan2f(float y, float x); /** * Compute the ceiling of `x`. * - * The ceiling of `x` is the smallest integer `y` such that `y > x`, i.e `x` + * The ceiling of `x` is the smallest integer `y` such that `y >= x`, i.e `x` * rounded up to the nearest integer. * * Domain: `-INF <= x <= INF` @@ -4750,7 +4750,7 @@ extern SDL_DECLSPEC double SDLCALL SDL_ceil(double x); /** * Compute the ceiling of `x`. * - * The ceiling of `x` is the smallest integer `y` such that `y > x`, i.e `x` + * The ceiling of `x` is the smallest integer `y` such that `y >= x`, i.e `x` * rounded up to the nearest integer. * * Domain: `-INF <= x <= INF` @@ -4992,7 +4992,7 @@ extern SDL_DECLSPEC float SDLCALL SDL_fabsf(float x); /** * Compute the floor of `x`. * - * The floor of `x` is the largest integer `y` such that `y > x`, i.e `x` + * The floor of `x` is the largest integer `y` such that `y <= x`, i.e `x` * rounded down to the nearest integer. * * Domain: `-INF <= x <= INF` @@ -5020,7 +5020,7 @@ extern SDL_DECLSPEC double SDLCALL SDL_floor(double x); /** * Compute the floor of `x`. * - * The floor of `x` is the largest integer `y` such that `y > x`, i.e `x` + * The floor of `x` is the largest integer `y` such that `y <= x`, i.e `x` * rounded down to the nearest integer. * * Domain: `-INF <= x <= INF` diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h index 69f4d69ca9..445142fee2 100644 --- a/include/SDL3/SDL_surface.h +++ b/include/SDL3/SDL_surface.h @@ -96,9 +96,10 @@ typedef enum SDL_ScaleMode */ typedef enum SDL_FlipMode { - SDL_FLIP_NONE, /**< Do not flip */ - SDL_FLIP_HORIZONTAL, /**< flip horizontally */ - SDL_FLIP_VERTICAL /**< flip vertically */ + SDL_FLIP_NONE, /**< Do not flip */ + SDL_FLIP_HORIZONTAL, /**< flip horizontally */ + SDL_FLIP_VERTICAL, /**< flip vertically */ + SDL_FLIP_HORIZONTAL_AND_VERTICAL = (SDL_FLIP_HORIZONTAL | SDL_FLIP_VERTICAL) /**< flip horizontally and vertically (not a diagonal flip) */ } SDL_FlipMode; #ifndef SDL_INTERNAL diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 36279b08df..d71a86e2ca 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -180,6 +180,7 @@ #cmakedefine HAVE_MEMFD_CREATE 1 #cmakedefine HAVE_POSIX_FALLOCATE 1 #cmakedefine HAVE_SIGACTION 1 +#cmakedefine HAVE_SIGTIMEDWAIT 1 #cmakedefine HAVE_SA_SIGACTION 1 #cmakedefine HAVE_ST_MTIM 1 #cmakedefine HAVE_SETJMP 1 @@ -223,6 +224,7 @@ #cmakedefine HAVE_WINDOWS_GAMING_INPUT_H 1 #cmakedefine HAVE_GAMEINPUT_H 1 #cmakedefine HAVE_DXGI_H 1 +#cmakedefine HAVE_DXGI1_5_H 1 #cmakedefine HAVE_DXGI1_6_H 1 #cmakedefine HAVE_MMDEVICEAPI_H 1 @@ -234,6 +236,8 @@ #cmakedefine HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR 1 #cmakedefine HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP 1 +#cmakedefine HAVE_DLOPEN_NOTES 1 + /* SDL internal assertion support */ #cmakedefine SDL_DEFAULT_ASSERT_LEVEL_CONFIGURED 1 #ifdef SDL_DEFAULT_ASSERT_LEVEL_CONFIGURED @@ -541,6 +545,9 @@ /* Enable dialog subsystem */ #cmakedefine SDL_DIALOG_DUMMY 1 +/* Enable tray subsystem */ +#cmakedefine SDL_TRAY_DUMMY 1 + /* Enable assembly routines */ #cmakedefine SDL_ALTIVEC_BLITTERS 1 diff --git a/include/build_config/SDL_build_config_android.h b/include/build_config/SDL_build_config_android.h index a0727f50cd..2ebb459115 100644 --- a/include/build_config/SDL_build_config_android.h +++ b/include/build_config/SDL_build_config_android.h @@ -208,6 +208,9 @@ #define SDL_CAMERA_DRIVER_ANDROID 1 #endif /* SDL_CAMERA_DISABLED */ +/* Enable tray subsystem */ +#define SDL_TRAY_DUMMY 1 + /* Enable nl_langinfo and high-res file times on version 26 and higher. */ #if __ANDROID_API__ >= 26 #define HAVE_NL_LANGINFO 1 diff --git a/include/build_config/SDL_build_config_ios.h b/include/build_config/SDL_build_config_ios.h index be1ec29a18..f278556a5f 100644 --- a/include/build_config/SDL_build_config_ios.h +++ b/include/build_config/SDL_build_config_ios.h @@ -223,4 +223,7 @@ /* Enable dialog subsystem */ #define SDL_DIALOG_DUMMY 1 +/* Enable tray subsystem */ +#define SDL_TRAY_DUMMY 1 + #endif /* SDL_build_config_ios_h_ */ diff --git a/include/build_config/SDL_build_config_minimal.h b/include/build_config/SDL_build_config_minimal.h index d5fe736e9d..e20d9c2ef1 100644 --- a/include/build_config/SDL_build_config_minimal.h +++ b/include/build_config/SDL_build_config_minimal.h @@ -98,4 +98,7 @@ typedef unsigned int uintptr_t; /* Enable dialog subsystem */ #define SDL_DIALOG_DUMMY 1 +/* Enable tray subsystem */ +#define SDL_TRAY_DUMMY 1 + #endif /* SDL_build_config_minimal_h_ */ diff --git a/include/build_config/SDL_build_config_windows.h b/include/build_config/SDL_build_config_windows.h index 98079cb178..7872d2a928 100644 --- a/include/build_config/SDL_build_config_windows.h +++ b/include/build_config/SDL_build_config_windows.h @@ -85,6 +85,7 @@ typedef unsigned int uintptr_t; #define HAVE_DXGI_H 1 #define HAVE_XINPUT_H 1 #if defined(_WIN32_MAXVER) && _WIN32_MAXVER >= 0x0A00 /* Windows 10 SDK */ +#define HAVE_DXGI1_5_H 1 #define HAVE_DXGI1_6_H 1 #define HAVE_WINDOWS_GAMING_INPUT_H 1 #endif diff --git a/include/build_config/SDL_build_config_wingdk.h b/include/build_config/SDL_build_config_wingdk.h index a5fb7e497a..734fb6c6c6 100644 --- a/include/build_config/SDL_build_config_wingdk.h +++ b/include/build_config/SDL_build_config_wingdk.h @@ -37,6 +37,7 @@ #define HAVE_DSOUND_H 1 /* No SDK version checks needed for these because the SDK has to be new. */ #define HAVE_DXGI_H 1 +#define HAVE_DXGI1_5_H 1 #define HAVE_DXGI1_6_H 1 #define HAVE_XINPUT_H 1 #define HAVE_WINDOWS_GAMING_INPUT_H 1 diff --git a/include/build_config/SDL_build_config_xbox.h b/include/build_config/SDL_build_config_xbox.h index 5a4fde297f..70e9047acf 100644 --- a/include/build_config/SDL_build_config_xbox.h +++ b/include/build_config/SDL_build_config_xbox.h @@ -220,4 +220,7 @@ /* Enable dialog subsystem */ #define SDL_DIALOG_DUMMY 1 +/* Enable tray subsystem */ +#define SDL_TRAY_DUMMY 1 + #endif /* SDL_build_config_wingdk_h_ */ diff --git a/src/SDL.c b/src/SDL.c index 4e93142b89..0a4e7993e5 100644 --- a/src/SDL.c +++ b/src/SDL.c @@ -237,6 +237,7 @@ static bool SDL_ShouldQuitSubsystem(Uint32 subsystem) return (((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 1)) || SDL_bInMainQuit); } +#if !defined(SDL_VIDEO_DISABLED) || !defined(SDL_AUDIO_DISABLED) || !defined(SDL_JOYSTICK_DISABLED) /* Private helper to either increment's existing ref counter, * or fully init a new subsystem. */ static bool SDL_InitOrIncrementSubsystem(Uint32 subsystem) @@ -252,6 +253,7 @@ static bool SDL_InitOrIncrementSubsystem(Uint32 subsystem) } return SDL_InitSubSystem(subsystem); } +#endif // !SDL_VIDEO_DISABLED || !SDL_AUDIO_DISABLED || !SDL_JOYSTICK_DISABLED void SDL_SetMainReady(void) { diff --git a/src/SDL_internal.h b/src/SDL_internal.h index 8fcd96a7fa..0d5d203f8f 100644 --- a/src/SDL_internal.h +++ b/src/SDL_internal.h @@ -63,6 +63,7 @@ #include "SDL_build_config.h" #include "dynapi/SDL_dynapi.h" +#include "dynapi/SDL_dynapi_dlopennote.h" #if SDL_DYNAMIC_API #include "dynapi/SDL_dynapi_overrides.h" diff --git a/src/atomic/SDL_atomic.c b/src/atomic/SDL_atomic.c index 7a575ac23a..c51ba710a5 100644 --- a/src/atomic/SDL_atomic.c +++ b/src/atomic/SDL_atomic.c @@ -298,6 +298,30 @@ int SDL_AddAtomicInt(SDL_AtomicInt *a, int v) #endif } +Uint32 SDL_AddAtomicU32(SDL_AtomicU32 *a, int v) +{ +#ifdef HAVE_MSC_ATOMICS + SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(long) == sizeof(a->value)); + return (Uint32)_InterlockedExchangeAdd((long *)&a->value, v); +#elif defined(HAVE_WATCOM_ATOMICS) + SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(int) == sizeof(a->value)); + return (Uint32)_SDL_xadd_watcom((volatile int *)&a->value, v); +#elif defined(HAVE_GCC_ATOMICS) + return __sync_fetch_and_add(&a->value, v); +#elif defined(SDL_PLATFORM_SOLARIS) + Uint32 pv = a->value; + membar_consumer(); + atomic_add_int((volatile uint_t *)&a->value, v); + return pv; +#else + Uint32 value; + do { + value = a->value; + } while (!SDL_CompareAndSwapAtomicU32(a, value, (value + v))); + return value; +#endif +} + int SDL_GetAtomicInt(SDL_AtomicInt *a) { #ifdef HAVE_ATOMIC_LOAD_N diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 8b0ecab202..cc20b47b12 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -186,16 +186,15 @@ void OnAudioStreamCreated(SDL_AudioStream *stream) // NOTE that you can create an audio stream without initializing the audio subsystem, // but it will not be automatically destroyed during a later call to SDL_Quit! // You must explicitly destroy it yourself! - if (current_audio.device_hash_lock) { - // this isn't really part of the "device list" but it's a convenient lock to use here. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + if (current_audio.subsystem_rwlock) { + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); if (current_audio.existing_streams) { current_audio.existing_streams->prev = stream; } stream->prev = NULL; stream->next = current_audio.existing_streams; current_audio.existing_streams = stream; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -206,9 +205,8 @@ void OnAudioStreamDestroy(SDL_AudioStream *stream) // NOTE that you can create an audio stream without initializing the audio subsystem, // but it will not be automatically destroyed during a later call to SDL_Quit! // You must explicitly destroy it yourself! - if (current_audio.device_hash_lock) { - // this isn't really part of the "device list" but it's a convenient lock to use here. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + if (current_audio.subsystem_rwlock) { + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); if (stream->prev) { stream->prev->next = stream->next; } @@ -218,7 +216,7 @@ void OnAudioStreamDestroy(SDL_AudioStream *stream) if (stream == current_audio.existing_streams) { current_audio.existing_streams = stream->next; } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -369,14 +367,28 @@ static SDL_AudioDeviceID AssignAudioDeviceInstanceId(bool recording, bool islogi bool SDL_IsAudioDevicePhysical(SDL_AudioDeviceID devid) { + // bit #1 of devid is set for physical devices and unset for logical. return (devid & (1 << 1)) != 0; } +static bool SDL_IsAudioDeviceLogical(SDL_AudioDeviceID devid) +{ + // bit #1 of devid is set for physical devices and unset for logical. + return (devid & (1 << 1)) == 0; +} + bool SDL_IsAudioDevicePlayback(SDL_AudioDeviceID devid) { + // bit #0 of devid is set for playback devices and unset for recording. return (devid & (1 << 0)) != 0; } +static bool SDL_IsAudioDeviceRecording(SDL_AudioDeviceID devid) +{ + // bit #0 of devid is set for playback devices and unset for recording. + return (devid & (1 << 0)) == 0; +} + static void ObtainPhysicalAudioDeviceObj(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXMEL SDL_ACQUIRE { if (device) { @@ -407,21 +419,19 @@ static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid, SDL_AudioDevice *device = NULL; SDL_LogicalAudioDevice *logdev = NULL; - // bit #1 of devid is set for physical devices and unset for logical. - const bool islogical = !(devid & (1<<1)); - if (islogical) { // don't bother looking if it's not a logical device id value. - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &logdev); + if (SDL_IsAudioDeviceLogical(devid)) { // don't bother looking if it's not a logical device id value. + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_FindInHashTable(current_audio.device_hash_logical, (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. } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (logdev) { - // we have to release the device_hash_lock before we take the device lock, to avoid deadlocks, so do a loop + // we have to release the subsystem_rwlock before we take the device lock, to avoid deadlocks, so do a loop // to make sure the correct physical device gets locked, in case we're in a race with the default changing. while (true) { SDL_LockMutex(device->lock); @@ -454,17 +464,15 @@ static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) // ! { SDL_AudioDevice *device = NULL; - // bit #1 of devid is set for physical devices and unset for logical. - const bool islogical = !(devid & (1<<1)); - if (islogical) { + if (SDL_IsAudioDeviceLogical(devid)) { ObtainLogicalAudioDevice(devid, &device); } else if (!SDL_GetCurrentAudioDriver()) { // (the `islogical` path, above, checks this in ObtainLogicalAudioDevice.) SDL_SetError("Audio subsystem is not initialized"); } 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); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_FindInHashTable(current_audio.device_hash_physical, (const void *) (uintptr_t) devid, (const void **) &device); + SDL_assert(!device || (device->instance_id == devid)); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (!device) { SDL_SetError("Invalid audio device instance ID"); @@ -486,13 +494,13 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI const SDL_AudioDeviceID orig_devid = devid; while (true) { - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) { devid = current_audio.default_playback_device_id; } else if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) { devid = current_audio.default_recording_device_id; } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (devid == 0) { SDL_SetError("No default audio device available"); @@ -506,13 +514,13 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI // make sure the default didn't change while we were waiting for the lock... bool got_it = false; - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) && (devid == current_audio.default_playback_device_id)) { got_it = true; } else if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) && (devid == current_audio.default_recording_device_id)) { got_it = true; } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (got_it) { return device; @@ -529,10 +537,10 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI static void DestroyLogicalAudioDevice(SDL_LogicalAudioDevice *logdev) { // Remove ourselves from the device_hash hashtable. - if (current_audio.device_hash) { // will be NULL while shutting down. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - SDL_RemoveFromHashTable(current_audio.device_hash, (const void *) (uintptr_t) logdev->instance_id); - SDL_UnlockRWLock(current_audio.device_hash_lock); + if (current_audio.device_hash_logical) { // will be NULL while shutting down. + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); + SDL_RemoveFromHashTable(current_audio.device_hash_logical, (const void *) (uintptr_t) logdev->instance_id); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } // remove ourselves from the physical device's list of logical devices. @@ -593,11 +601,11 @@ void UnrefPhysicalAudioDevice(SDL_AudioDevice *device) { if (SDL_AtomicDecRef(&device->refcount)) { // take it out of the device list. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - if (SDL_RemoveFromHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id)) { + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); + if (SDL_RemoveFromHashTable(current_audio.device_hash_physical, (const void *) (uintptr_t) device->instance_id)) { SDL_AddAtomicInt(device->recording ? ¤t_audio.recording_device_count : ¤t_audio.playback_device_count, -1); } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); DestroyPhysicalAudioDevice(device); // ...and nuke it. } } @@ -611,9 +619,9 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recordi { SDL_assert(name != NULL); - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); const int shutting_down = SDL_GetAtomicInt(¤t_audio.shutting_down); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (shutting_down) { return NULL; // we're shutting down, don't add any devices that are hotplugged at the last possible moment. } @@ -655,8 +663,8 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recordi device->instance_id = AssignAudioDeviceInstanceId(recording, /*islogical=*/false); - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - if (SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id, device, false)) { + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); + if (SDL_InsertIntoHashTable(current_audio.device_hash_physical, (const void *) (uintptr_t) device->instance_id, device, false)) { SDL_AddAtomicInt(device_count, 1); } else { SDL_DestroyCondition(device->close_cond); @@ -665,7 +673,7 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recordi SDL_free(device); device = NULL; } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); RefPhysicalAudioDevice(device); // unref'd on device disconnect. return device; @@ -713,12 +721,12 @@ SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_ p->type = SDL_EVENT_AUDIO_DEVICE_ADDED; p->devid = device->instance_id; p->next = NULL; - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_assert(current_audio.pending_events_tail != NULL); SDL_assert(current_audio.pending_events_tail->next == NULL); current_audio.pending_events_tail->next = p; current_audio.pending_events_tail = p; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -742,10 +750,10 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) ObtainPhysicalAudioDeviceObj(device); - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); const SDL_AudioDeviceID devid = device->instance_id; const bool is_default_device = ((devid == current_audio.default_playback_device_id) || (devid == current_audio.default_recording_device_id)); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); const bool first_disconnect = SDL_CompareAndSwapAtomicInt(&device->zombie, 0, 1); if (first_disconnect) { // if already disconnected this device, don't do it twice. @@ -790,12 +798,12 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) if (first_disconnect) { if (pending.next) { // NULL if event is disabled or disaster struck. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_assert(current_audio.pending_events_tail != NULL); SDL_assert(current_audio.pending_events_tail->next == NULL); current_audio.pending_events_tail->next = pending.next; current_audio.pending_events_tail = pending_tail; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } UnrefPhysicalAudioDevice(device); @@ -881,11 +889,8 @@ static bool SDLCALL FindLowestDeviceID(void *userdata, const SDL_HashTable *tabl { FindLowestDeviceIDData *data = (FindLowestDeviceIDData *) userdata; const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - // bit #0 of devid is set for playback devices and unset for recording. - // bit #1 of devid is set for physical devices and unset for logical. - const bool devid_recording = !(devid & (1 << 0)); - const bool isphysical = !!(devid & (1 << 1)); - if (isphysical && (devid_recording == data->recording) && (devid < data->highest)) { + SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical. + if ((SDL_IsAudioDeviceRecording(devid) == data->recording) && (devid < data->highest)) { data->highest = devid; data->result = (SDL_AudioDevice *) value; SDL_assert(data->result->instance_id == devid); @@ -899,9 +904,9 @@ static SDL_AudioDevice *GetFirstAddedAudioDevice(const bool recording) // (Device IDs increase as new devices are added, so the first device added has the lowest SDL_AudioDeviceID value.) FindLowestDeviceIDData data = { recording, highest, NULL }; - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_IterateHashTable(current_audio.device_hash, FindLowestDeviceID, &data); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_IterateHashTable(current_audio.device_hash_physical, FindLowestDeviceID, &data); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); return data.result; } @@ -925,14 +930,21 @@ bool SDL_InitAudio(const char *driver_name) SDL_ChooseAudioConverters(); SDL_SetupAudioResampler(); - SDL_RWLock *device_hash_lock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole audio subsystem. - if (!device_hash_lock) { + SDL_RWLock *subsystem_rwlock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole audio subsystem. + if (!subsystem_rwlock) { return false; } - SDL_HashTable *device_hash = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL); - if (!device_hash) { - SDL_DestroyRWLock(device_hash_lock); + SDL_HashTable *device_hash_physical = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL); + if (!device_hash_physical) { + SDL_DestroyRWLock(subsystem_rwlock); + return false; + } + + SDL_HashTable *device_hash_logical = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL); + if (!device_hash_logical) { + SDL_DestroyHashTable(device_hash_physical); + SDL_DestroyRWLock(subsystem_rwlock); return false; } @@ -949,8 +961,9 @@ bool SDL_InitAudio(const char *driver_name) const char *driver_attempt = driver_name_copy; if (!driver_name_copy) { - SDL_DestroyRWLock(device_hash_lock); - SDL_DestroyHashTable(device_hash); + SDL_DestroyRWLock(subsystem_rwlock); + SDL_DestroyHashTable(device_hash_physical); + SDL_DestroyHashTable(device_hash_logical); return false; } @@ -972,8 +985,9 @@ bool SDL_InitAudio(const char *driver_name) tried_to_init = true; SDL_zero(current_audio); current_audio.pending_events_tail = ¤t_audio.pending_events; - current_audio.device_hash_lock = device_hash_lock; - current_audio.device_hash = device_hash; + current_audio.subsystem_rwlock = subsystem_rwlock; + current_audio.device_hash_physical = device_hash_physical; + current_audio.device_hash_logical = device_hash_logical; if (bootstrap[i]->init(¤t_audio.impl)) { current_audio.name = bootstrap[i]->name; current_audio.desc = bootstrap[i]->desc; @@ -996,8 +1010,9 @@ bool SDL_InitAudio(const char *driver_name) tried_to_init = true; SDL_zero(current_audio); current_audio.pending_events_tail = ¤t_audio.pending_events; - current_audio.device_hash_lock = device_hash_lock; - current_audio.device_hash = device_hash; + current_audio.subsystem_rwlock = subsystem_rwlock; + current_audio.device_hash_physical = device_hash_physical; + current_audio.device_hash_logical = device_hash_logical; if (bootstrap[i]->init(¤t_audio.impl)) { current_audio.name = bootstrap[i]->name; current_audio.desc = bootstrap[i]->desc; @@ -1018,8 +1033,9 @@ bool SDL_InitAudio(const char *driver_name) } } - SDL_DestroyRWLock(device_hash_lock); - SDL_DestroyHashTable(device_hash); + SDL_DestroyRWLock(subsystem_rwlock); + SDL_DestroyHashTable(device_hash_physical); + SDL_DestroyHashTable(device_hash_logical); SDL_zero(current_audio); return false; // No driver was available, so fail. } @@ -1055,15 +1071,11 @@ bool SDL_InitAudio(const char *driver_name) static bool SDLCALL DestroyOnePhysicalAudioDevice(void *userdata, const SDL_HashTable *table, const void *key, const void *value) { - // bit #1 of devid is set for physical devices and unset for logical. const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - const bool isphysical = !!(devid & (1<<1)); - if (isphysical) { - SDL_AudioDevice *dev = (SDL_AudioDevice *) value; - - SDL_assert(dev->instance_id == devid); - DestroyPhysicalAudioDevice(dev); - } + SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical. + SDL_AudioDevice *dev = (SDL_AudioDevice *) value; + SDL_assert(dev->instance_id == devid); + DestroyPhysicalAudioDevice(dev); return true; // keep iterating. } @@ -1087,15 +1099,16 @@ void SDL_QuitAudio(void) } } - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_SetAtomicInt(¤t_audio.shutting_down, 1); - SDL_HashTable *device_hash = current_audio.device_hash; - current_audio.device_hash = NULL; + SDL_HashTable *device_hash_physical = current_audio.device_hash_physical; + SDL_HashTable *device_hash_logical = current_audio.device_hash_logical; + current_audio.device_hash_physical = current_audio.device_hash_logical = NULL; SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next; current_audio.pending_events.next = NULL; SDL_SetAtomicInt(¤t_audio.playback_device_count, 0); SDL_SetAtomicInt(¤t_audio.recording_device_count, 0); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); SDL_PendingAudioDeviceEvent *pending_next = NULL; for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) { @@ -1103,13 +1116,15 @@ void SDL_QuitAudio(void) SDL_free(i); } - SDL_IterateHashTable(device_hash, DestroyOnePhysicalAudioDevice, NULL); + SDL_IterateHashTable(device_hash_physical, DestroyOnePhysicalAudioDevice, NULL); + // device_hash_* will _not_ be empty because we nulled them out in current_audio, but all their items are now free'd pointers. Just destroy the hashes, below. // Free the driver data current_audio.impl.Deinitialize(); - SDL_DestroyRWLock(current_audio.device_hash_lock); - SDL_DestroyHashTable(device_hash); + SDL_DestroyRWLock(current_audio.subsystem_rwlock); + SDL_DestroyHashTable(device_hash_physical); + SDL_DestroyHashTable(device_hash_logical); SDL_zero(current_audio); } @@ -1417,13 +1432,10 @@ static bool SDLCALL CountAudioDevices(void *userdata, const SDL_HashTable *table { CountAudioDevicesData *data = (CountAudioDevicesData *) userdata; const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - // bit #0 of devid is set for playback devices and unset for recording. - // bit #1 of devid is set for physical devices and unset for logical. - const bool devid_recording = !(devid & (1<<0)); - const bool isphysical = !!(devid & (1<<1)); - if (isphysical && (devid_recording == data->recording)) { + SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical. + if (SDL_IsAudioDeviceRecording(devid) == data->recording) { SDL_assert(data->devs_seen < data->num_devices); - SDL_AudioDevice *device = (SDL_AudioDevice *) value; // this is normally risky, but we hold the device_hash_lock here. + SDL_AudioDevice *device = (SDL_AudioDevice *) value; // this is normally risky, but we hold the subsystem_rwlock here. const bool zombie = SDL_GetAtomicInt(&device->zombie) != 0; if (zombie) { data->devs_skipped++; @@ -1440,19 +1452,19 @@ static SDL_AudioDeviceID *GetAudioDevices(int *count, bool recording) int num_devices = 0; if (SDL_GetCurrentAudioDriver()) { - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); { num_devices = SDL_GetAtomicInt(recording ? ¤t_audio.recording_device_count : ¤t_audio.playback_device_count); result = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID)); if (result) { CountAudioDevicesData data = { 0, 0, num_devices, result, recording }; - SDL_IterateHashTable(current_audio.device_hash, CountAudioDevices, &data); + SDL_IterateHashTable(current_audio.device_hash_physical, CountAudioDevices, &data); SDL_assert((data.devs_seen + data.devs_skipped) == num_devices); num_devices = data.devs_seen; // might be less if we skipped any. result[num_devices] = 0; // null-terminated. } } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } else { SDL_SetError("Audio subsystem is not initialized"); } @@ -1488,15 +1500,12 @@ static bool SDLCALL FindAudioDeviceByCallback(void *userdata, const SDL_HashTabl { FindAudioDeviceByCallbackData *data = (FindAudioDeviceByCallbackData *) userdata; const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - // bit #1 of devid is set for physical devices and unset for logical. - const bool isphysical = !!(devid & (1<<1)); - if (isphysical) { - 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. - } + SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical. + 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. } return true; // keep iterating. } @@ -1510,9 +1519,9 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(bool (*callback)(SDL_Audi } FindAudioDeviceByCallbackData data = { callback, userdata, NULL }; - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_IterateHashTable(current_audio.device_hash, FindAudioDeviceByCallback, &data); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_IterateHashTable(current_audio.device_hash_physical, FindAudioDeviceByCallback, &data); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (!data.retval) { SDL_SetError("Device not found"); @@ -1534,19 +1543,20 @@ 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; - const void *vdev = NULL; if (!SDL_GetCurrentAudioDriver()) { SDL_SetError("Audio subsystem is not initialized"); } else { + const bool islogical = SDL_IsAudioDeviceLogical(devid); + const void *vdev = NULL; + // This does not call ObtainPhysicalAudioDevice() because the device's name never changes, so // it doesn't have to lock the whole device. However, just to make sure the device pointer itself // 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, &vdev); + // subsystem_rwlock while we copy the string. + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); + SDL_FindInHashTable(islogical ? current_audio.device_hash_logical : current_audio.device_hash_physical, (const void *) (uintptr_t) devid, &vdev); if (!vdev) { SDL_SetError("Invalid audio device instance ID"); } else if (islogical) { @@ -1558,7 +1568,7 @@ const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) SDL_assert(device->instance_id == devid); result = SDL_GetPersistentString(device->name); } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } return result; @@ -1833,8 +1843,7 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp // this will let you use a logical device to make a new logical device on the parent physical device. Could be useful? SDL_AudioDevice *device = NULL; - const bool islogical = (!wants_default && !(devid & (1<<1))); - if (!islogical) { + if ((wants_default || SDL_IsAudioDevicePhysical(devid))) { device = ObtainPhysicalAudioDeviceDefaultAllowed(devid); } else { SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); @@ -1871,9 +1880,9 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp ReleaseAudioDevice(device); if (result) { - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - const bool inserted = SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) result, logdev, false); - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); + const bool inserted = SDL_InsertIntoHashTable(current_audio.device_hash_logical, (const void *) (uintptr_t) result, logdev, false); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (!inserted) { SDL_CloseAudioDevice(result); result = 0; @@ -1971,7 +1980,6 @@ bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallba bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *streams, int num_streams) { - const bool islogical = !(devid & (1<<1)); SDL_AudioDevice *device = NULL; SDL_LogicalAudioDevice *logdev = NULL; bool result = true; @@ -1982,7 +1990,7 @@ bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *stre return SDL_InvalidParamError("num_streams"); } else if (!streams) { return SDL_InvalidParamError("streams"); - } else if (!islogical) { + } else if (SDL_IsAudioDevicePhysical(devid)) { return SDL_SetError("Audio streams are bound to device ids from SDL_OpenAudioDevice, not raw physical devices"); } @@ -2323,7 +2331,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) const bool recording = new_default_device->recording; // change the official default over right away, so new opens will go to the new device. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); const SDL_AudioDeviceID current_devid = recording ? current_audio.default_recording_device_id : current_audio.default_playback_device_id; const bool is_already_default = (new_default_device->instance_id == current_devid); if (!is_already_default) { @@ -2333,7 +2341,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) current_audio.default_playback_device_id = new_default_device->instance_id; } } - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (is_already_default) { return; // this is already the default. @@ -2399,8 +2407,8 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) continue; // not opened as a default, leave it on the current physical device. } - // now migrate the logical device. Hold device_hash_lock so ObtainLogicalAudioDevice doesn't get a device in the middle of transition. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + // now migrate the logical device. Hold subsystem_rwlock so ObtainLogicalAudioDevice doesn't get a device in the middle of transition. + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); if (logdev->next) { logdev->next->prev = logdev->prev; } @@ -2415,7 +2423,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) logdev->prev = NULL; logdev->next = new_default_device->logical_devices; new_default_device->logical_devices = logdev; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); SDL_assert(SDL_GetAtomicInt(¤t_default_device->refcount) > 1); // we should hold at least one extra reference to this device, beyond logical devices, during this phase... RefPhysicalAudioDevice(new_default_device); @@ -2457,12 +2465,12 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) } if (pending.next) { - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_assert(current_audio.pending_events_tail != NULL); SDL_assert(current_audio.pending_events_tail->next == NULL); current_audio.pending_events_tail->next = pending.next; current_audio.pending_events_tail = pending_tail; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -2539,12 +2547,12 @@ bool SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SD } if (pending.next) { - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); SDL_assert(current_audio.pending_events_tail != NULL); SDL_assert(current_audio.pending_events_tail->next == NULL); current_audio.pending_events_tail->next = pending.next; current_audio.pending_events_tail = pending_tail; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); } } @@ -2566,20 +2574,20 @@ bool SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec * // ("UpdateSubsystem" is the same naming that the other things that hook into PumpEvents use.) void SDL_UpdateAudio(void) { - SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_LockRWLockForReading(current_audio.subsystem_rwlock); SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); if (!pending_events) { return; // nothing to do, check next time. } // okay, let's take this whole list of events so we can dump the lock, and new ones can queue up for a later update. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); + SDL_LockRWLockForWriting(current_audio.subsystem_rwlock); pending_events = current_audio.pending_events.next; // in case this changed... current_audio.pending_events.next = NULL; current_audio.pending_events_tail = ¤t_audio.pending_events; - SDL_UnlockRWLock(current_audio.device_hash_lock); + SDL_UnlockRWLock(current_audio.subsystem_rwlock); SDL_PendingAudioDeviceEvent *pending_next = NULL; for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) { @@ -2589,7 +2597,7 @@ void SDL_UpdateAudio(void) SDL_zero(event); event.type = i->type; event.adevice.which = (Uint32) i->devid; - event.adevice.recording = ((i->devid & (1<<0)) == 0); // bit #0 of devid is set for playback devices and unset for recording. + event.adevice.recording = SDL_IsAudioDeviceRecording(i->devid); // bit #0 of devid is set for playback devices and unset for recording. SDL_PushEvent(&event); } SDL_free(i); diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 3b3cb9b37e..1b6dd10805 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -183,8 +183,9 @@ typedef struct SDL_AudioDriver const char *name; // The name of this audio driver const char *desc; // The description of this audio driver SDL_AudioDriverImpl impl; // the backend's interface - SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash` - SDL_HashTable *device_hash; // the collection of currently-available audio devices (recording, playback, logical and physical!) + SDL_RWLock *subsystem_rwlock; // A rwlock that protects several things in the audio subsystem (device hashtables, etc). + SDL_HashTable *device_hash_physical; // the collection of currently-available audio devices (recording and playback), for mapping SDL_AudioDeviceID to an SDL_AudioDevice*. + SDL_HashTable *device_hash_logical; // the collection of currently-available audio devices (recording and playback), for mapping SDL_AudioDeviceID to an SDL_LogicalAudioDevice*. SDL_AudioStream *existing_streams; // a list of all existing SDL_AudioStreams. SDL_AudioDeviceID default_playback_device_id; SDL_AudioDeviceID default_recording_device_id; diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c index b1be69182a..41c50cd630 100644 --- a/src/audio/aaudio/SDL_aaudio.c +++ b/src/audio/aaudio/SDL_aaudio.c @@ -53,6 +53,13 @@ struct SDL_PrivateAudioData #define LIB_AAUDIO_SO "libaaudio.so" +SDL_ELF_NOTE_DLOPEN( + "audio-aaudio", + "Support for audio through AAudio", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + LIB_AAUDIO_SO +); + typedef struct AAUDIO_Data { SDL_SharedObject *handle; @@ -313,8 +320,8 @@ 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 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 diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c index 97783cd92f..07b1b556f4 100644 --- a/src/audio/alsa/SDL_alsa_audio.c +++ b/src/audio/alsa/SDL_alsa_audio.c @@ -82,6 +82,7 @@ static int (*ALSA_snd_pcm_nonblock)(snd_pcm_t *, int); static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int); static int (*ALSA_snd_pcm_sw_params_set_avail_min)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t); static int (*ALSA_snd_pcm_reset)(snd_pcm_t *); +static snd_pcm_state_t (*ALSA_snd_pcm_state)(snd_pcm_t *); static int (*ALSA_snd_device_name_hint)(int, const char *, void ***); static char *(*ALSA_snd_device_name_get_hint)(const void *, const char *); static int (*ALSA_snd_device_name_free_hint)(void **); @@ -171,6 +172,7 @@ static bool load_alsa_syms(void) SDL_ALSA_SYM(snd_pcm_wait); SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min); SDL_ALSA_SYM(snd_pcm_reset); + SDL_ALSA_SYM(snd_pcm_state); SDL_ALSA_SYM(snd_device_name_hint); SDL_ALSA_SYM(snd_device_name_get_hint); SDL_ALSA_SYM(snd_device_name_free_hint); @@ -207,6 +209,13 @@ static bool load_alsa_syms(void) #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libalsa", + "Support for audio through libalsa", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_ALSA_DYNAMIC +); + static void UnloadALSALibrary(void) { if (alsa_handle) { @@ -345,6 +354,20 @@ static char *get_pcm_str(void *handle) return pcm_str; } +static int RecoverALSADevice(snd_pcm_t *pcm, int errnum) +{ + const snd_pcm_state_t prerecovery = ALSA_snd_pcm_state(pcm); + const int status = ALSA_snd_pcm_recover(pcm, errnum, 0); // !!! FIXME: third parameter is non-zero to prevent libasound from printing error messages. Should we do that? + if (status == 0) { + const snd_pcm_state_t postrecovery = ALSA_snd_pcm_state(pcm); + if ((prerecovery == SND_PCM_STATE_XRUN) && (postrecovery == SND_PCM_STATE_PREPARED)) { + ALSA_snd_pcm_start(pcm); // restart the device if it stopped due to an overrun or underrun. + } + } + return status; +} + + // This function waits until it is possible to write a full sound buffer static bool ALSA_WaitDevice(SDL_AudioDevice *device) { @@ -355,7 +378,7 @@ static bool ALSA_WaitDevice(SDL_AudioDevice *device) while (!SDL_GetAtomicInt(&device->shutdown)) { const int rc = ALSA_snd_pcm_avail(device->hidden->pcm); if (rc < 0) { - const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0); + const int status = RecoverALSADevice(device->hidden->pcm, rc); if (status < 0) { // Hmm, not much we can do - abort SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA wait failed (unrecoverable): %s", ALSA_snd_strerror(rc)); @@ -383,7 +406,7 @@ static bool ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int bu SDL_assert(rc != 0); // assuming this can't happen if we used snd_pcm_wait and queried for available space. if (rc < 0) { SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it! - const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0); + const int status = RecoverALSADevice(device->hidden->pcm, rc); if (status < 0) { // Hmm, not much we can do - abort SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(rc)); @@ -438,7 +461,7 @@ static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it! if (rc < 0) { - const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0); + const int status = RecoverALSADevice(device->hidden->pcm, rc); if (status < 0) { // Hmm, not much we can do - abort SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA read failed (unrecoverable): %s", ALSA_snd_strerror(rc)); diff --git a/src/audio/jack/SDL_jackaudio.c b/src/audio/jack/SDL_jackaudio.c index 3ae5137a58..49dff5fc91 100644 --- a/src/audio/jack/SDL_jackaudio.c +++ b/src/audio/jack/SDL_jackaudio.c @@ -50,6 +50,13 @@ static bool load_jack_syms(void); #ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libjack", + "Support for audio through libjack", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_JACK_DYNAMIC +); + static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC; static SDL_SharedObject *jack_handle = NULL; diff --git a/src/audio/openslES/SDL_openslES.c b/src/audio/openslES/SDL_openslES.c index 4d5b3bdfd1..e58ab8b048 100644 --- a/src/audio/openslES/SDL_openslES.c +++ b/src/audio/openslES/SDL_openslES.c @@ -426,28 +426,33 @@ static void OPENSLES_DestroyPCMPlayer(SDL_AudioDevice *device) static bool OPENSLES_CreatePCMPlayer(SDL_AudioDevice *device) { - /* If we want to add floating point audio support (requires API level 21) - it can be done as described here: - https://developer.android.com/ndk/guides/audio/opensl/android-extensions.html#floating-point - */ + /* according to https://developer.android.com/ndk/guides/audio/opensl/opensl-for-android, + Android's OpenSL ES only supports Uint8 and _littleendian_ Sint16. + (and float32, with an extension we use, below.) */ if (SDL_GetAndroidSDKVersion() >= 21) { const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); SDL_AudioFormat test_format; while ((test_format = *(closefmts++)) != 0) { - if (SDL_AUDIO_ISSIGNED(test_format)) { + switch (test_format) { + case SDL_AUDIO_U8: + case SDL_AUDIO_S16LE: + case SDL_AUDIO_F32: break; + default: + continue; } + break; } if (!test_format) { // Didn't find a compatible format : - LOGI("No compatible audio format, using signed 16-bit audio"); - test_format = SDL_AUDIO_S16; + LOGI("No compatible audio format, using signed 16-bit LE audio"); + test_format = SDL_AUDIO_S16LE; } device->spec.format = test_format; } else { // Just go with signed 16-bit audio as it's the most compatible - device->spec.format = SDL_AUDIO_S16; + device->spec.format = SDL_AUDIO_S16LE; } // Update the fragment size as size in bytes diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c index a9e95a6fe1..2257263137 100644 --- a/src/audio/pipewire/SDL_pipewire.c +++ b/src/audio/pipewire/SDL_pipewire.c @@ -93,6 +93,13 @@ static int (*PIPEWIRE_pw_properties_setf)(struct pw_properties *, const char *, #ifdef SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libpipewire", + "Support for audio through libpipewire", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC +); + static const char *pipewire_library = SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC; static SDL_SharedObject *pipewire_handle = NULL; diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index debcdf27d6..7c2b99172d 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -133,6 +133,13 @@ static bool load_pulseaudio_syms(void); #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libpulseaudio", + "Support for audio through libpulseaudio", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC +); + static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC; static SDL_SharedObject *pulseaudio_handle = NULL; diff --git a/src/audio/sndio/SDL_sndioaudio.c b/src/audio/sndio/SDL_sndioaudio.c index a0d20209b5..1cc7ac7551 100644 --- a/src/audio/sndio/SDL_sndioaudio.c +++ b/src/audio/sndio/SDL_sndioaudio.c @@ -108,6 +108,13 @@ static bool load_sndio_syms(void) #ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "audio-libsndio", + "Support for audio through libsndio", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_AUDIO_DRIVER_SNDIO_DYNAMIC +); + static void UnloadSNDIOLibrary(void) { if (sndio_handle) { diff --git a/src/camera/SDL_camera.c b/src/camera/SDL_camera.c index 48c6b500df..855c9be6aa 100644 --- a/src/camera/SDL_camera.c +++ b/src/camera/SDL_camera.c @@ -270,7 +270,7 @@ static void ClosePhysicalCamera(SDL_Camera *device) SDL_aligned_free(device->zombie_pixels); - device->permission = 0; + device->permission = SDL_CAMERA_PERMISSION_STATE_PENDING; device->zombie_pixels = NULL; device->filled_output_surfaces.next = NULL; device->empty_output_surfaces.next = NULL; @@ -581,7 +581,7 @@ void SDL_CameraPermissionOutcome(SDL_Camera *device, bool approved) pending.next = NULL; SDL_PendingCameraEvent *pending_tail = &pending; - const int permission = approved ? 1 : -1; + const SDL_CameraPermissionState permission = approved ? SDL_CAMERA_PERMISSION_STATE_APPROVED : SDL_CAMERA_PERMISSION_STATE_DENIED; ObtainPhysicalCameraObj(device); if (device->permission != permission) { @@ -665,7 +665,7 @@ bool SDL_GetCameraFormat(SDL_Camera *camera, SDL_CameraSpec *spec) SDL_Camera *device = camera; // currently there's no separation between physical and logical device. ObtainPhysicalCameraObj(device); - if (device->permission > 0) { + if (device->permission > SDL_CAMERA_PERMISSION_STATE_PENDING) { SDL_copyp(spec, &device->spec); result = true; } else { @@ -808,9 +808,9 @@ bool SDL_CameraThreadIterate(SDL_Camera *device) } const int permission = device->permission; - if (permission <= 0) { + if (permission <= SDL_CAMERA_PERMISSION_STATE_PENDING) { SDL_UnlockMutex(device->lock); - return (permission < 0) ? false : true; // if permission was denied, shut it down. if undecided, we're done for now. + return (permission < SDL_CAMERA_PERMISSION_STATE_PENDING) ? false : true; // if permission was denied, shut it down. if undecided, we're done for now. } bool failed = false; // set to true if disaster worthy of treating the device as lost has happened. @@ -1264,7 +1264,7 @@ SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS) ObtainPhysicalCameraObj(device); - if (device->permission <= 0) { + if (device->permission <= SDL_CAMERA_PERMISSION_STATE_PENDING) { ReleaseCamera(device); SDL_SetError("Camera permission has not been granted"); return NULL; @@ -1371,12 +1371,12 @@ SDL_PropertiesID SDL_GetCameraProperties(SDL_Camera *camera) return result; } -int SDL_GetCameraPermissionState(SDL_Camera *camera) +SDL_CameraPermissionState SDL_GetCameraPermissionState(SDL_Camera *camera) { - int result; + SDL_CameraPermissionState result; if (!camera) { SDL_InvalidParamError("camera"); - result = -1; + result = SDL_CAMERA_PERMISSION_STATE_DENIED; } else { SDL_Camera *device = camera; // currently there's no separation between physical and logical device. ObtainPhysicalCameraObj(device); diff --git a/src/camera/SDL_syscamera.h b/src/camera/SDL_syscamera.h index 30a02f391a..094ad0faa9 100644 --- a/src/camera/SDL_syscamera.h +++ b/src/camera/SDL_syscamera.h @@ -160,8 +160,8 @@ struct SDL_Camera // Optional properties. SDL_PropertiesID props; - // -1: user denied permission, 0: waiting for user response, 1: user approved permission. - int permission; + // Current state of user permission check. + SDL_CameraPermissionState permission; // Data private to this driver, used when device is opened and running. struct SDL_PrivateCameraData *hidden; diff --git a/src/camera/coremedia/SDL_camera_coremedia.m b/src/camera/coremedia/SDL_camera_coremedia.m index f58fb92784..94b7110970 100644 --- a/src/camera/coremedia/SDL_camera_coremedia.m +++ b/src/camera/coremedia/SDL_camera_coremedia.m @@ -85,7 +85,7 @@ static void CoreMediaFormatToSDL(FourCharCode fmt, SDL_PixelFormat *pixel_format static bool CheckCameraPermissions(SDL_Camera *device) { - if (device->permission == 0) { // still expecting a permission result. + if (device->permission == SDL_CAMERA_PERMISSION_STATE_PENDING) { // still expecting a permission result. if (@available(macOS 14, *)) { const AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; if (status != AVAuthorizationStatusNotDetermined) { // NotDetermined == still waiting for an answer from the user. @@ -96,7 +96,7 @@ static bool CheckCameraPermissions(SDL_Camera *device) } } - return (device->permission > 0); + return (device->permission > SDL_CAMERA_PERMISSION_STATE_PENDING); } // this delegate just receives new video frames on a Grand Central Dispatch queue, and fires off the diff --git a/src/camera/pipewire/SDL_camera_pipewire.c b/src/camera/pipewire/SDL_camera_pipewire.c index 056ed4f7e1..ad98f3967e 100644 --- a/src/camera/pipewire/SDL_camera_pipewire.c +++ b/src/camera/pipewire/SDL_camera_pipewire.c @@ -109,6 +109,13 @@ static int (*PIPEWIRE_pw_properties_setf)(struct pw_properties *, const char *, #ifdef SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "camera-libpipewire", + "Support for camera through libpipewire", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC +); + static const char *pipewire_library = SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC; static SDL_SharedObject *pipewire_handle = NULL; diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index e0745a0a57..6e9d2e7389 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -1827,7 +1827,10 @@ size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOSta const int bytes = AAsset_read((AAsset *)userdata, buffer, size); if (bytes < 0) { SDL_SetError("AAsset_read() failed"); + *status = SDL_IO_STATUS_ERROR; return 0; + } else if (bytes < size) { + *status = SDL_IO_STATUS_EOF; } return (size_t)bytes; } @@ -1835,6 +1838,7 @@ size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOSta size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status) { SDL_SetError("Cannot write to Android package filesystem"); + *status = SDL_IO_STATUS_ERROR; return 0; } diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index b61a1cd920..b675f18724 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -24,12 +24,20 @@ #ifdef SDL_USE_LIBDBUS // we never link directly to libdbus. -static const char *dbus_library = "libdbus-1.so.3"; +#define SDL_DRIVER_DBUS_DYNAMIC "libdbus-1.so.3" +static const char *dbus_library = SDL_DRIVER_DBUS_DYNAMIC; static SDL_SharedObject *dbus_handle = NULL; static char *inhibit_handle = NULL; static unsigned int screensaver_cookie = 0; static SDL_DBusContext dbus; +SDL_ELF_NOTE_DLOPEN( + "core-libdbus", + "Support for D-Bus IPC", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_DRIVER_DBUS_DYNAMIC +); + static bool LoadDBUSSyms(void) { #define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \ diff --git a/src/core/linux/SDL_udev.c b/src/core/linux/SDL_udev.c index f32a4fdf64..2903ec34b4 100644 --- a/src/core/linux/SDL_udev.c +++ b/src/core/linux/SDL_udev.c @@ -36,7 +36,22 @@ #include "SDL_evdev_capabilities.h" #include "../unix/SDL_poll.h" -static const char *SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" }; +#define SDL_UDEV_FALLBACK_LIBS "libudev.so.1", "libudev.so.0" + +static const char *SDL_UDEV_LIBS[] = { SDL_UDEV_FALLBACK_LIBS }; + +#ifdef SDL_UDEV_DYNAMIC +#define SDL_UDEV_DLNOTE_LIBS SDL_UDEV_DYNAMIC, SDL_UDEV_FALLBACK_LIBS +#else +#define SDL_UDEV_DLNOTE_LIBS SDL_UDEV_FALLBACK_LIBS +#endif + +SDL_ELF_NOTE_DLOPEN( + "events-udev", + "Support for events through libudev", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_UDEV_DLNOTE_LIBS +); static SDL_UDEV_PrivateData *_this = NULL; diff --git a/src/core/windows/SDL_windows.c b/src/core/windows/SDL_windows.c index cc8c8327ce..b402a94c1f 100644 --- a/src/core/windows/SDL_windows.c +++ b/src/core/windows/SDL_windows.c @@ -24,6 +24,8 @@ #include "SDL_windows.h" +#include "../../video/SDL_surface_c.h" + #include // for CoInitialize/CoUninitialize (Win32 only) #ifdef HAVE_ROAPI_H #include // For RoInitialize/RoUninitialize (Win32 only) @@ -53,6 +55,44 @@ typedef enum RO_INIT_TYPE #define WC_ERR_INVALID_CHARS 0x00000080 #endif +// Dark mode support +typedef enum { + UXTHEME_APPMODE_DEFAULT, + UXTHEME_APPMODE_ALLOW_DARK, + UXTHEME_APPMODE_FORCE_DARK, + UXTHEME_APPMODE_FORCE_LIGHT, + UXTHEME_APPMODE_MAX +} UxthemePreferredAppMode; + +typedef enum { + WCA_UNDEFINED = 0, + WCA_USEDARKMODECOLORS = 26, + WCA_LAST = 27 +} WINDOWCOMPOSITIONATTRIB; + +typedef struct { + WINDOWCOMPOSITIONATTRIB Attrib; + PVOID pvData; + SIZE_T cbData; +} WINDOWCOMPOSITIONATTRIBDATA; + +typedef struct { + ULONG dwOSVersionInfoSize; + ULONG dwMajorVersion; + ULONG dwMinorVersion; + ULONG dwBuildNumber; + ULONG dwPlatformId; + WCHAR szCSDVersion[128]; +} NT_OSVERSIONINFOW; + +typedef bool (WINAPI *ShouldAppsUseDarkMode_t)(void); +typedef void (WINAPI *AllowDarkModeForWindow_t)(HWND, bool); +typedef void (WINAPI *AllowDarkModeForApp_t)(bool); +typedef void (WINAPI *RefreshImmersiveColorPolicyState_t)(void); +typedef UxthemePreferredAppMode (WINAPI *SetPreferredAppMode_t)(UxthemePreferredAppMode); +typedef BOOL (WINAPI *SetWindowCompositionAttribute_t)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *); +typedef void (NTAPI *RtlGetVersion_t)(NT_OSVERSIONINFOW *); + // Fake window to help with DirectInput events. HWND SDL_HelperWindow = NULL; static const TCHAR *SDL_HelperWindowClassName = TEXT("SDLHelperWindowInputCatcher"); @@ -408,6 +448,148 @@ bool WIN_WindowRectValid(const RECT *rect) return (rect->right > 0); } +void WIN_UpdateDarkModeForHWND(HWND hwnd) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll")); + if (!ntdll) { + return; + } + // There is no function to get Windows build number, so let's get it here via RtlGetVersion + RtlGetVersion_t RtlGetVersionFunc = (RtlGetVersion_t)GetProcAddress(ntdll, "RtlGetVersion"); + NT_OSVERSIONINFOW os_info; + os_info.dwOSVersionInfoSize = sizeof(NT_OSVERSIONINFOW); + os_info.dwBuildNumber = 0; + if (RtlGetVersionFunc) { + RtlGetVersionFunc(&os_info); + } + FreeLibrary(ntdll); + os_info.dwBuildNumber &= ~0xF0000000; + if (os_info.dwBuildNumber < 17763) { + // Too old to support dark mode + return; + } + HMODULE uxtheme = LoadLibrary(TEXT("uxtheme.dll")); + if (!uxtheme) { + return; + } + RefreshImmersiveColorPolicyState_t RefreshImmersiveColorPolicyStateFunc = (RefreshImmersiveColorPolicyState_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(104)); + ShouldAppsUseDarkMode_t ShouldAppsUseDarkModeFunc = (ShouldAppsUseDarkMode_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(132)); + AllowDarkModeForWindow_t AllowDarkModeForWindowFunc = (AllowDarkModeForWindow_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(133)); + if (os_info.dwBuildNumber < 18362) { + AllowDarkModeForApp_t AllowDarkModeForAppFunc = (AllowDarkModeForApp_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135)); + if (AllowDarkModeForAppFunc) { + AllowDarkModeForAppFunc(true); + } + } else { + SetPreferredAppMode_t SetPreferredAppModeFunc = (SetPreferredAppMode_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135)); + if (SetPreferredAppModeFunc) { + SetPreferredAppModeFunc(UXTHEME_APPMODE_ALLOW_DARK); + } + } + if (RefreshImmersiveColorPolicyStateFunc) { + RefreshImmersiveColorPolicyStateFunc(); + } + if (AllowDarkModeForWindowFunc) { + AllowDarkModeForWindowFunc(hwnd, true); + } + BOOL value; + // Check dark mode using ShouldAppsUseDarkMode, but use SDL_GetSystemTheme as a fallback + if (ShouldAppsUseDarkModeFunc) { + value = ShouldAppsUseDarkModeFunc() ? TRUE : FALSE; + } else { + value = (SDL_GetSystemTheme() == SDL_SYSTEM_THEME_DARK) ? TRUE : FALSE; + } + FreeLibrary(uxtheme); + if (os_info.dwBuildNumber < 18362) { + SetProp(hwnd, TEXT("UseImmersiveDarkModeColors"), SDL_reinterpret_cast(HANDLE, SDL_static_cast(INT_PTR, value))); + } else { + HMODULE user32 = GetModuleHandle(TEXT("user32.dll")); + if (user32) { + SetWindowCompositionAttribute_t SetWindowCompositionAttributeFunc = (SetWindowCompositionAttribute_t)GetProcAddress(user32, "SetWindowCompositionAttribute"); + if (SetWindowCompositionAttributeFunc) { + WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &value, sizeof(value) }; + SetWindowCompositionAttributeFunc(hwnd, &data); + } + } + } +#endif +} + +HICON WIN_CreateIconFromSurface(SDL_Surface *surface) +{ +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + SDL_Surface *s = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888); + if (!s) { + return NULL; + } + + /* The dimensions will be needed after s is freed */ + const int width = s->w; + const int height = s->h; + + BITMAPINFO bmpInfo; + SDL_zero(bmpInfo); + bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmpInfo.bmiHeader.biWidth = width; + bmpInfo.bmiHeader.biHeight = -height; /* Top-down bitmap */ + bmpInfo.bmiHeader.biPlanes = 1; + bmpInfo.bmiHeader.biBitCount = 32; + bmpInfo.bmiHeader.biCompression = BI_RGB; + + HDC hdc = GetDC(NULL); + void *pBits = NULL; + HBITMAP hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, &pBits, NULL, 0); + if (!hBitmap) { + ReleaseDC(NULL, hdc); + SDL_DestroySurface(s); + return NULL; + } + + SDL_memcpy(pBits, s->pixels, width * height * 4); + + SDL_DestroySurface(s); + + HBITMAP hMask = CreateBitmap(width, height, 1, 1, NULL); + if (!hMask) { + DeleteObject(hBitmap); + ReleaseDC(NULL, hdc); + return NULL; + } + + HDC hdcMem = CreateCompatibleDC(hdc); + HGDIOBJ oldBitmap = SelectObject(hdcMem, hMask); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + BYTE* pixel = (BYTE*)pBits + (y * width + x) * 4; + BYTE alpha = pixel[3]; + COLORREF maskColor = (alpha == 0) ? RGB(0, 0, 0) : RGB(255, 255, 255); + SetPixel(hdcMem, x, y, maskColor); + } + } + + ICONINFO iconInfo; + iconInfo.fIcon = TRUE; + iconInfo.xHotspot = 0; + iconInfo.yHotspot = 0; + iconInfo.hbmMask = hMask; + iconInfo.hbmColor = hBitmap; + + HICON hIcon = CreateIconIndirect(&iconInfo); + + SelectObject(hdcMem, oldBitmap); + DeleteDC(hdcMem); + DeleteObject(hBitmap); + DeleteObject(hMask); + ReleaseDC(NULL, hdc); + + return hIcon; +#else + return NULL; +#endif +} + // Some GUIDs we need to know without linking to libraries that aren't available before Vista. /* *INDENT-OFF* */ // clang-format off static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; diff --git a/src/core/windows/SDL_windows.h b/src/core/windows/SDL_windows.h index ef54fe379e..1cd1aca334 100644 --- a/src/core/windows/SDL_windows.h +++ b/src/core/windows/SDL_windows.h @@ -157,7 +157,11 @@ extern void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect); extern void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect); // Returns false if a window client rect is not valid -bool WIN_WindowRectValid(const RECT *rect); +extern bool WIN_WindowRectValid(const RECT *rect); + +extern void WIN_UpdateDarkModeForHWND(HWND hwnd); + +extern HICON WIN_CreateIconFromSurface(SDL_Surface *surface); extern SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat); diff --git a/src/dialog/unix/SDL_portaldialog.c b/src/dialog/unix/SDL_portaldialog.c index dadfc9f556..a7f96a3381 100644 --- a/src/dialog/unix/SDL_portaldialog.c +++ b/src/dialog/unix/SDL_portaldialog.c @@ -26,6 +26,8 @@ #ifdef SDL_USE_LIBDBUS #include +#include +#include #include #include #include @@ -294,7 +296,12 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false); const char *default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); + char *location_name = NULL; + char *location_folder = NULL; + struct stat statbuf; bool open_folders = false; + bool save_file_existing = false; + bool save_file_new_named = false; switch (type) { case SDL_FILEDIALOG_OPENFILE: @@ -305,6 +312,28 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog case SDL_FILEDIALOG_SAVEFILE: method = "SaveFile"; method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Save File"); + if (default_location) { + if (stat(default_location, &statbuf) == 0) { + save_file_existing = S_ISREG(statbuf.st_mode); + } else if (errno == ENOENT) { + char *dirc = SDL_strdup(default_location); + if (dirc) { + location_folder = SDL_strdup(dirname(dirc)); + SDL_free(dirc); + if (location_folder) { + save_file_new_named = (stat(location_folder, &statbuf) == 0) && S_ISDIR(statbuf.st_mode); + } + } + } + + if (save_file_existing || save_file_new_named) { + char *basec = SDL_strdup(default_location); + if (basec) { + location_name = SDL_strdup(basename(basec)); + SDL_free(basec); + } + } + } break; case SDL_FILEDIALOG_OPENFOLDER: @@ -317,10 +346,11 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog /* This is already checked in ../SDL_dialog.c; this silences compiler warnings */ SDL_SetError("Invalid file dialog type: %d", type); callback(userdata, NULL, -1); - return; + goto cleanup; } SDL_DBusContext *dbus = SDL_DBus_GetContext(); + DBusError error; DBusMessage *msg; DBusMessageIter params, options; const char *signal_id = NULL; @@ -332,23 +362,25 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog const char *err_msg = validate_filters(filters, nfilters); + dbus->error_init(&error); + if (err_msg) { SDL_SetError("%s", err_msg); callback(userdata, NULL, -1); - return; + goto cleanup; } if (dbus == NULL) { SDL_SetError("Failed to connect to DBus"); callback(userdata, NULL, -1); - return; + goto cleanup; } msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, PORTAL_INTERFACE, method); if (msg == NULL) { SDL_SetError("Failed to send message to portal"); callback(userdata, NULL, -1); - return; + goto cleanup; } dbus->message_iter_init_append(msg, ¶ms); @@ -362,7 +394,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog handle_str = SDL_malloc(len * sizeof(char)); if (!handle_str) { callback(userdata, NULL, -1); - return; + goto cleanup; } SDL_snprintf(handle_str, len, "%s%s", WAYLAND_HANDLE_PREFIX, parent_handle); @@ -373,7 +405,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog handle_str = SDL_malloc(len * sizeof(char)); if (!handle_str) { callback(userdata, NULL, -1); - return; + goto cleanup; } // The portal wants X11 window ID numbers in hex. @@ -393,7 +425,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog handle_str = SDL_malloc(sizeof(char) * (HANDLE_LEN + 1)); if (!handle_str) { callback(userdata, NULL, -1); - return; + goto cleanup; } SDL_snprintf(handle_str, HANDLE_LEN, "%u", ++handle_id); DBus_AppendStringOption(dbus, &options, "handle_token", handle_str); @@ -410,14 +442,34 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog DBus_AppendFilters(dbus, &options, filters, nfilters); } if (default_location) { - DBus_AppendByteArray(dbus, &options, "current_folder", default_location); + if (save_file_existing && location_name) { + /* Open a save dialog at an existing file */ + DBus_AppendByteArray(dbus, &options, "current_file", default_location); + /* Setting "current_name" should not be necessary however the kde-desktop-portal sets the filename without an extension. + * An alternative would be to match the extension to a filter and set "current_filter". + */ + DBus_AppendStringOption(dbus, &options, "current_name", location_name); + } else if (save_file_new_named && location_folder && location_name) { + /* Open a save dialog at a location with a suggested name */ + DBus_AppendByteArray(dbus, &options, "current_folder", location_folder); + DBus_AppendStringOption(dbus, &options, "current_name", location_name); + } else { + DBus_AppendByteArray(dbus, &options, "current_folder", default_location); + } } if (accept) { DBus_AppendStringOption(dbus, &options, "accept_label", accept); } dbus->message_iter_close_container(¶ms, &options); - DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_INFINITE, NULL); + DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_INFINITE, &error); + if (dbus->error_is_set(&error)) { + SDL_SetError("Failed to open dialog via DBus, %s: %s", error.name, error.message); + dbus->error_free(&error); + callback(userdata, NULL, -1); + goto cleanup; + } + if (reply) { DBusMessageIter reply_iter; dbus->message_iter_init(reply, &reply_iter); @@ -443,9 +495,16 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog } SDL_snprintf(filter, filter_len, SIGNAL_FILTER"%s'", signal_id); - dbus->bus_add_match(dbus->session_conn, filter, NULL); + dbus->bus_add_match(dbus->session_conn, filter, &error); SDL_free(filter); + if (dbus->error_is_set(&error)) { + SDL_SetError("Failed to set up DBus listener for dialog, %s: %s", error.name, error.message); + dbus->error_free(&error); + callback(userdata, NULL, -1); + goto cleanup; + } + SignalCallback *data = SDL_malloc(sizeof(SignalCallback)); if (!data) { callback(userdata, NULL, -1); @@ -469,6 +528,10 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog incorrect_type: dbus->message_unref(reply); + +cleanup: + SDL_free(location_name); + SDL_free(location_folder); } bool SDL_Portal_detect(void) diff --git a/src/dialog/windows/SDL_windowsdialog.c b/src/dialog/windows/SDL_windowsdialog.c index 2f4a12e813..d1d034ea6f 100644 --- a/src/dialog/windows/SDL_windowsdialog.c +++ b/src/dialog/windows/SDL_windowsdialog.c @@ -25,9 +25,176 @@ #include #include #include +#include #include "../../core/windows/SDL_windows.h" #include "../../thread/SDL_systhread.h" +// Flags/GUIDs defined for compatibility with pre-Vista headers +#ifndef __IFileDialog_INTERFACE_DEFINED__ +enum _FILEOPENDIALOGOPTIONS { + FOS_OVERWRITEPROMPT = 0x2, + FOS_STRICTFILETYPES = 0x4, + FOS_NOCHANGEDIR = 0x8, + FOS_PICKFOLDERS = 0x20, + FOS_FORCEFILESYSTEM = 0x40, + FOS_ALLNONSTORAGEITEMS = 0x80, + FOS_NOVALIDATE = 0x100, + FOS_ALLOWMULTISELECT = 0x200, + FOS_PATHMUSTEXIST = 0x800, + FOS_FILEMUSTEXIST = 0x1000, + FOS_CREATEPROMPT = 0x2000, + FOS_SHAREAWARE = 0x4000, + FOS_NOREADONLYRETURN = 0x8000, + FOS_NOTESTFILECREATE = 0x10000, + FOS_HIDEMRUPLACES = 0x20000, + FOS_HIDEPINNEDPLACES = 0x40000, + FOS_NODEREFERENCELINKS = 0x100000, + FOS_DONTADDTORECENT = 0x2000000, + FOS_FORCESHOWHIDDEN = 0x10000000, + FOS_DEFAULTNOMINIMODE = 0x20000000, + FOS_FORCEPREVIEWPANEON = 0x40000000, + FOS_SUPPORTSTREAMABLEITEMS = 0x80000000 +}; + +typedef DWORD FILEOPENDIALOGOPTIONS; + +typedef enum FDAP { + FDAP_BOTTOM = 0, + FDAP_TOP = 1 +} FDAP; + +/* *INDENT-OFF* */ // clang-format off +typedef struct IFileDialogVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(IFileDialog *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(IFileDialog *); + ULONG (STDMETHODCALLTYPE *Release)(IFileDialog *); + HRESULT (STDMETHODCALLTYPE *Show)(IFileDialog *, HWND); + HRESULT (STDMETHODCALLTYPE *SetFileTypes)(IFileDialog *, UINT, const COMDLG_FILTERSPEC *); + HRESULT (STDMETHODCALLTYPE *SetFileTypeIndex)(IFileDialog *, UINT); + HRESULT (STDMETHODCALLTYPE *GetFileTypeIndex)(IFileDialog *, UINT *); + HRESULT (STDMETHODCALLTYPE *Advise)(IFileDialog *, IFileDialogEvents *, DWORD *); + HRESULT (STDMETHODCALLTYPE *Unadvise)(IFileDialog *, DWORD); + HRESULT (STDMETHODCALLTYPE *SetOptions)(IFileDialog *, FILEOPENDIALOGOPTIONS); + HRESULT (STDMETHODCALLTYPE *GetOptions)(IFileDialog *, FILEOPENDIALOGOPTIONS *); + HRESULT (STDMETHODCALLTYPE *SetDefaultFolder)(IFileDialog *, IShellItem *); + HRESULT (STDMETHODCALLTYPE *SetFolder)(IFileDialog *, IShellItem *); + HRESULT (STDMETHODCALLTYPE *GetFolder)(IFileDialog *, IShellItem **); + HRESULT (STDMETHODCALLTYPE *GetCurrentSelection)(IFileDialog *, IShellItem **); + HRESULT (STDMETHODCALLTYPE *SetFileName)(IFileDialog *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *GetFileName)(IFileDialog *, LPWSTR *); + HRESULT (STDMETHODCALLTYPE *SetTitle)(IFileDialog *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *SetOkButtonLabel)(IFileDialog *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *SetFileNameLabel)(IFileDialog *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *GetResult)(IFileDialog *, IShellItem **); + HRESULT (STDMETHODCALLTYPE *AddPlace)(IFileDialog *, IShellItem *, FDAP); + HRESULT (STDMETHODCALLTYPE *SetDefaultExtension)(IFileDialog *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *Close)(IFileDialog *, HRESULT); + HRESULT (STDMETHODCALLTYPE *SetClientGuid)(IFileDialog *, REFGUID); + HRESULT (STDMETHODCALLTYPE *ClearClientData)(IFileDialog *); + HRESULT (STDMETHODCALLTYPE *SetFilter)(IFileDialog *,IShellItemFilter *); +} IFileDialogVtbl; +/* *INDENT-ON* */ // clang-format on + +struct IFileDialog +{ + const struct IFileDialogVtbl *lpVtbl; +}; +#endif + +#ifndef __IFileDialog2_INTERFACE_DEFINED__ +/* *INDENT-OFF* */ // clang-format off +typedef struct IFileDialog2Vtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(IFileDialog2 *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(IFileDialog2 *); + ULONG (STDMETHODCALLTYPE *Release)(IFileDialog2 *); + HRESULT (STDMETHODCALLTYPE *Show)(IFileDialog2 *, HWND); + HRESULT (STDMETHODCALLTYPE *SetFileTypes)(IFileDialog2 *, UINT, const COMDLG_FILTERSPEC *); + HRESULT (STDMETHODCALLTYPE *SetFileTypeIndex)(IFileDialog2 *, UINT); + HRESULT (STDMETHODCALLTYPE *GetFileTypeIndex)(IFileDialog2 *, UINT *); + HRESULT (STDMETHODCALLTYPE *Advise)(IFileDialog2 *, IFileDialogEvents *, DWORD *); + HRESULT (STDMETHODCALLTYPE *Unadvise)(IFileDialog2 *, DWORD); + HRESULT (STDMETHODCALLTYPE *SetOptions)(IFileDialog2 *, FILEOPENDIALOGOPTIONS); + HRESULT (STDMETHODCALLTYPE *GetOptions)(IFileDialog2 *, FILEOPENDIALOGOPTIONS *); + HRESULT (STDMETHODCALLTYPE *SetDefaultFolder)(IFileDialog2 *, IShellItem *); + HRESULT (STDMETHODCALLTYPE *SetFolder)(IFileDialog2 *, IShellItem *); + HRESULT (STDMETHODCALLTYPE *GetFolder)(IFileDialog2 *, IShellItem **); + HRESULT (STDMETHODCALLTYPE *GetCurrentSelection)(IFileDialog2 *, IShellItem **); + HRESULT (STDMETHODCALLTYPE *SetFileName)(IFileDialog2 *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *GetFileName)(IFileDialog2 *, LPWSTR *); + HRESULT (STDMETHODCALLTYPE *SetTitle)(IFileDialog2 *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *SetOkButtonLabel)(IFileDialog2 *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *SetFileNameLabel)(IFileDialog2 *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *GetResult)(IFileDialog2 *, IShellItem **); + HRESULT (STDMETHODCALLTYPE *AddPlace)(IFileDialog2 *, IShellItem *, FDAP); + HRESULT (STDMETHODCALLTYPE *SetDefaultExtension)(IFileDialog2 *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *Close)(IFileDialog2 *, HRESULT); + HRESULT (STDMETHODCALLTYPE *SetClientGuid)(IFileDialog2 *, REFGUID); + HRESULT (STDMETHODCALLTYPE *ClearClientData)(IFileDialog2 *); + HRESULT (STDMETHODCALLTYPE *SetFilter)(IFileDialog2 *, IShellItemFilter *); + HRESULT (STDMETHODCALLTYPE *SetCancelButtonLabel)(IFileDialog2 *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *SetNavigationRoot)(IFileDialog2 *, IShellItem *); +} IFileDialog2Vtbl; +/* *INDENT-ON* */ // clang-format on + +struct IFileDialog2 +{ + const struct IFileDialog2Vtbl *lpVtbl; +}; +#endif + +#ifndef __IFileOpenDialog_INTERFACE_DEFINED__ +/* *INDENT-OFF* */ // clang-format off +typedef struct IFileOpenDialogVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(IFileOpenDialog *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(IFileOpenDialog *); + ULONG (STDMETHODCALLTYPE *Release)(IFileOpenDialog *); + HRESULT (STDMETHODCALLTYPE *Show)(IFileOpenDialog *, HWND); + HRESULT (STDMETHODCALLTYPE *SetFileTypes)(IFileOpenDialog *, UINT, const COMDLG_FILTERSPEC *); + HRESULT (STDMETHODCALLTYPE *SetFileTypeIndex)(IFileOpenDialog *, UINT); + HRESULT (STDMETHODCALLTYPE *GetFileTypeIndex)(IFileOpenDialog *, UINT *); + HRESULT (STDMETHODCALLTYPE *Advise)(IFileOpenDialog *, IFileDialogEvents *, DWORD *); + HRESULT (STDMETHODCALLTYPE *Unadvise)(IFileOpenDialog *, DWORD); + HRESULT (STDMETHODCALLTYPE *SetOptions)(IFileOpenDialog *, FILEOPENDIALOGOPTIONS); + HRESULT (STDMETHODCALLTYPE *GetOptions)(IFileOpenDialog *, FILEOPENDIALOGOPTIONS *); + HRESULT (STDMETHODCALLTYPE *SetDefaultFolder)(IFileOpenDialog *, IShellItem *); + HRESULT (STDMETHODCALLTYPE *SetFolder)(IFileOpenDialog *, IShellItem *); + HRESULT (STDMETHODCALLTYPE *GetFolder)(IFileOpenDialog *, IShellItem **); + HRESULT (STDMETHODCALLTYPE *GetCurrentSelection)(IFileOpenDialog *, IShellItem **); + HRESULT (STDMETHODCALLTYPE *SetFileName)(IFileOpenDialog *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *GetFileName)(IFileOpenDialog *, LPWSTR *); + HRESULT (STDMETHODCALLTYPE *SetTitle)(IFileOpenDialog *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *SetOkButtonLabel)(IFileOpenDialog *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *SetFileNameLabel)(IFileOpenDialog *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *GetResult)(IFileOpenDialog *, IShellItem **); + HRESULT (STDMETHODCALLTYPE *AddPlace)(IFileOpenDialog *, IShellItem *, FDAP); + HRESULT (STDMETHODCALLTYPE *SetDefaultExtension)(IFileOpenDialog *, LPCWSTR); + HRESULT (STDMETHODCALLTYPE *Close)(IFileOpenDialog *, HRESULT); + HRESULT (STDMETHODCALLTYPE *SetClientGuid)(IFileOpenDialog *, REFGUID); + HRESULT (STDMETHODCALLTYPE *ClearClientData)(IFileOpenDialog *); + HRESULT (STDMETHODCALLTYPE *SetFilter)(IFileOpenDialog *, IShellItemFilter *); + HRESULT (STDMETHODCALLTYPE *GetResults)(IFileOpenDialog *, IShellItemArray **); + HRESULT (STDMETHODCALLTYPE *GetSelectedItems)(IFileOpenDialog *, IShellItemArray **); +} IFileOpenDialogVtbl; +/* *INDENT-ON* */ // clang-format on + +struct IFileOpenDialog +{ + const struct IFileOpenDialogVtbl *lpVtbl; +}; +#endif + +/* *INDENT-OFF* */ // clang-format off +static const CLSID SDL_CLSID_FileOpenDialog = { 0xdc1c5a9c, 0xe88a, 0x4dde, { 0xa5, 0xa1, 0x60, 0xf8, 0x2a, 0x20, 0xae, 0xf7 } }; +static const CLSID SDL_CLSID_FileSaveDialog = { 0xc0b4e2f3, 0xba21, 0x4773, { 0x8d, 0xba, 0x33, 0x5e, 0xc9, 0x46, 0xeb, 0x8b } }; + +static const IID SDL_IID_IFileDialog = { 0x42f85136, 0xdb7e, 0x439c, { 0x85, 0xf1, 0xe4, 0x07, 0x5d, 0x13, 0x5f, 0xc8 } }; +static const IID SDL_IID_IFileDialog2 = { 0x61744fc7, 0x85b5, 0x4791, { 0xa9, 0xb0, 0x27, 0x22, 0x76, 0x30, 0x9b, 0x13 } }; +static const IID SDL_IID_IFileOpenDialog = { 0xd57c7288, 0xd4ad, 0x4768, { 0xbe, 0x02, 0x9d, 0x96, 0x95, 0x32, 0xd9, 0x60 } }; +/* *INDENT-ON* */ // clang-format on + // If this number is too small, selecting too many files will give an error #define SELECTLIST_SIZE 65536 @@ -35,9 +202,11 @@ typedef struct { bool is_save; wchar_t *filters_str; + int nfilters; char *default_file; SDL_Window *parent; DWORD flags; + bool allow_many; SDL_DialogFileCallback callback; void *userdata; char *title; @@ -48,6 +217,7 @@ typedef struct typedef struct { SDL_Window *parent; + bool allow_many; SDL_DialogFileCallback callback; char *default_folder; void *userdata; @@ -101,18 +271,334 @@ char *clear_filt_names(const char *filt) return cleared; } +bool windows_ShowModernFileFolderDialog(SDL_FileDialogType dialog_type, const char *default_file, SDL_Window *parent, bool allow_many, SDL_DialogFileCallback callback, void *userdata, const char *title, const char *accept, const char *cancel, wchar_t *filter_wchar, int nfilters) +{ + bool is_save = dialog_type == SDL_FILEDIALOG_SAVEFILE; + bool is_folder = dialog_type == SDL_FILEDIALOG_OPENFOLDER; + + if (is_save) { + // Just in case; the code relies on that + allow_many = false; + } + + HMODULE shell32_handle = NULL; + + typedef HRESULT(WINAPI *pfnSHCreateItemFromParsingName)(PCWSTR, IBindCtx *, REFIID, void **); + pfnSHCreateItemFromParsingName pSHCreateItemFromParsingName = NULL; + + IFileDialog *pFileDialog = NULL; + IFileOpenDialog *pFileOpenDialog = NULL; + IFileDialog2 *pFileDialog2 = NULL; + IShellItemArray *pItemArray = NULL; + IShellItem *pItem = NULL; + IShellItem *pFolderItem = NULL; + LPWSTR filePath = NULL; + COMDLG_FILTERSPEC *filter_data = NULL; + char **files = NULL; + wchar_t *title_w = NULL; + wchar_t *accept_w = NULL; + wchar_t *cancel_w = NULL; + FILEOPENDIALOGOPTIONS pfos; + + wchar_t *default_file_w = NULL; + wchar_t *default_folder_w = NULL; + + bool success = false; + bool co_init = false; + + // We can assume shell32 is already loaded here. + shell32_handle = GetModuleHandle(TEXT("shell32.dll")); + if (!shell32_handle) { + goto quit; + } + + pSHCreateItemFromParsingName = (pfnSHCreateItemFromParsingName)GetProcAddress(shell32_handle, "SHCreateItemFromParsingName"); + if (!pSHCreateItemFromParsingName) { + goto quit; + } + + if (filter_wchar && nfilters > 0) { + wchar_t *filter_ptr = filter_wchar; + filter_data = SDL_calloc(sizeof(COMDLG_FILTERSPEC), nfilters); + if (!filter_data) { + goto quit; + } + + for (int i = 0; i < nfilters; i++) { + filter_data[i].pszName = filter_ptr; + filter_ptr += SDL_wcslen(filter_ptr) + 1; + filter_data[i].pszSpec = filter_ptr; + filter_ptr += SDL_wcslen(filter_ptr) + 1; + } + + // assert(*filter_ptr == L'\0'); + } + + if (title && (title_w = WIN_UTF8ToStringW(title)) == NULL) { + goto quit; + } + + if (accept && (accept_w = WIN_UTF8ToStringW(accept)) == NULL) { + goto quit; + } + + if (cancel && (cancel_w = WIN_UTF8ToStringW(cancel)) == NULL) { + goto quit; + } + + if (default_file) { + default_folder_w = WIN_UTF8ToStringW(default_file); + + if (!default_folder_w) { + goto quit; + } + + for (wchar_t *chrptr = default_folder_w; *chrptr; chrptr++) { + if (*chrptr == L'/' || *chrptr == L'\\') { + default_file_w = chrptr; + } + } + + if (!default_file_w) { + default_file_w = default_folder_w; + default_folder_w = NULL; + } else { + *default_file_w = L'\0'; + default_file_w++; + + if (SDL_wcslen(default_file_w) == 0) { + default_file_w = NULL; + } + } + } + +#define CHECK(op) if (!SUCCEEDED(op)) { goto quit; } + + CHECK(WIN_CoInitialize()); + + co_init = true; + + CHECK(CoCreateInstance(is_save ? &SDL_CLSID_FileSaveDialog : &SDL_CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_IFileDialog, (void**)&pFileDialog)); + CHECK(pFileDialog->lpVtbl->QueryInterface(pFileDialog, &SDL_IID_IFileDialog2, (void**)&pFileDialog2)); + + if (allow_many) { + CHECK(pFileDialog->lpVtbl->QueryInterface(pFileDialog, &SDL_IID_IFileOpenDialog, (void**)&pFileOpenDialog)); + } + + CHECK(pFileDialog2->lpVtbl->GetOptions(pFileDialog2, &pfos)); + + pfos |= FOS_NOCHANGEDIR; + if (allow_many) pfos |= FOS_ALLOWMULTISELECT; + if (is_save) pfos |= FOS_OVERWRITEPROMPT; + if (is_folder) pfos |= FOS_PICKFOLDERS; + + CHECK(pFileDialog2->lpVtbl->SetOptions(pFileDialog2, pfos)); + + if (cancel_w) { + CHECK(pFileDialog2->lpVtbl->SetCancelButtonLabel(pFileDialog2, cancel_w)); + } + + if (accept_w) { + CHECK(pFileDialog->lpVtbl->SetOkButtonLabel(pFileDialog, accept_w)); + } + + if (title_w) { + CHECK(pFileDialog->lpVtbl->SetTitle(pFileDialog, title_w)); + } + + if (filter_data) { + CHECK(pFileDialog->lpVtbl->SetFileTypes(pFileDialog, nfilters, filter_data)); + } + + // SetFolder would enforce using the same location each and every time, but + // Windows docs recommend against it + if (default_folder_w) { + CHECK(pSHCreateItemFromParsingName(default_folder_w, NULL, &IID_IShellItem, (void**)&pFolderItem)); + CHECK(pFileDialog->lpVtbl->SetDefaultFolder(pFileDialog, pFolderItem)); + } + + if (default_file_w) { + CHECK(pFileDialog->lpVtbl->SetFileName(pFileDialog, default_file_w)); + } + + if (parent) { + HWND window = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); + + HRESULT hr = pFileDialog->lpVtbl->Show(pFileDialog, window); + + if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { + const char * const results[] = { NULL }; + UINT selected_filter; + + // This is a one-based index, not zero-based. Doc link in similar comment below + CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); + callback(userdata, results, selected_filter - 1); + success = true; + goto quit; + } else if (!SUCCEEDED(hr)) { + goto quit; + } + } else { + HRESULT hr = pFileDialog->lpVtbl->Show(pFileDialog, NULL); + + if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { + const char * const results[] = { NULL }; + UINT selected_filter; + + // This is a one-based index, not zero-based. Doc link in similar comment below + CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); + callback(userdata, results, selected_filter - 1); + success = true; + goto quit; + } else if (!SUCCEEDED(hr)) { + goto quit; + } + } + + if (allow_many) { + DWORD nResults; + UINT selected_filter; + + CHECK(pFileOpenDialog->lpVtbl->GetResults(pFileOpenDialog, &pItemArray)); + CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); + CHECK(pItemArray->lpVtbl->GetCount(pItemArray, &nResults)); + + files = SDL_calloc(nResults + 1, sizeof(char*)); + if (!files) { + goto quit; + } + char** files_ptr = files; + + for (DWORD i = 0; i < nResults; i++) { + CHECK(pItemArray->lpVtbl->GetItemAt(pItemArray, i, &pItem)); + CHECK(pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &filePath)); + + *(files_ptr++) = WIN_StringToUTF8(filePath); + + CoTaskMemFree(filePath); + filePath = NULL; + pItem->lpVtbl->Release(pItem); + pItem = NULL; + } + + callback(userdata, (const char * const *) files, selected_filter - 1); + success = true; + } else { + // This is a one-based index, not zero-based. + // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-getfiletypeindex#parameters + UINT selected_filter; + + CHECK(pFileDialog->lpVtbl->GetResult(pFileDialog, &pItem)); + CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); + CHECK(pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &filePath)); + + char *file = WIN_StringToUTF8(filePath); + if (!file) { + goto quit; + } + const char * const results[] = { file, NULL }; + callback(userdata, results, selected_filter - 1); + success = true; + SDL_free(file); + } + + success = true; + +#undef CHECK + +quit: + if (!success) { + WIN_SetError("dialogg"); + callback(userdata, NULL, -1); + } + + if (co_init) { + WIN_CoUninitialize(); + } + + if (pFileOpenDialog) { + pFileOpenDialog->lpVtbl->Release(pFileOpenDialog); + } + + if (pFileDialog2) { + pFileDialog2->lpVtbl->Release(pFileDialog2); + } + + if (pFileDialog) { + pFileDialog->lpVtbl->Release(pFileDialog); + } + + if (pItem) { + pItem->lpVtbl->Release(pItem); + } + + if (pFolderItem) { + pFolderItem->lpVtbl->Release(pFolderItem); + } + + if (pItemArray) { + pItemArray->lpVtbl->Release(pItemArray); + } + + if (filePath) { + CoTaskMemFree(filePath); + } + + // If both default_file_w and default_folder_w are non-NULL, then + // default_file_w is a pointer into default_folder_w. + if (default_folder_w) { + SDL_free(default_folder_w); + } else if (default_file_w) { + SDL_free(default_file_w); + } + + if (title_w) { + SDL_free(title_w); + } + + if (accept_w) { + SDL_free(accept_w); + } + + if (cancel_w) { + SDL_free(cancel_w); + } + + if (filter_data) { + SDL_free(filter_data); + } + + if (files) { + for (char** files_ptr = files; *files_ptr; files_ptr++) { + SDL_free(*files_ptr); + } + SDL_free(files); + } + + return success; +} + // TODO: The new version of file dialogs void windows_ShowFileDialog(void *ptr) { + winArgs *args = (winArgs *) ptr; bool is_save = args->is_save; const char *default_file = args->default_file; SDL_Window *parent = args->parent; DWORD flags = args->flags; + bool allow_many = args->allow_many; SDL_DialogFileCallback callback = args->callback; void *userdata = args->userdata; const char *title = args->title; + const char *accept = args->accept; + const char *cancel = args->cancel; wchar_t *filter_wchar = args->filters_str; + int nfilters = args->nfilters; + + if (windows_ShowModernFileFolderDialog(is_save ? SDL_FILEDIALOG_SAVEFILE : SDL_FILEDIALOG_OPENFILE, default_file, parent, allow_many, callback, userdata, title, accept, cancel, filter_wchar, nfilters)) { + return; + } /* GetOpenFileName and GetSaveFileName have the same signature (yes, LPOPENFILENAMEW even for the save dialog) */ @@ -407,7 +893,15 @@ void windows_ShowFolderDialog(void *ptr) SDL_DialogFileCallback callback = args->callback; void *userdata = args->userdata; HWND parent = NULL; + int allow_many = args->allow_many; + char *default_folder = args->default_folder; const char *title = args->title; + const char *accept = args->accept; + const char *cancel = args->cancel; + + if (windows_ShowModernFileFolderDialog(SDL_FILEDIALOG_OPENFOLDER, default_folder, window, allow_many, callback, userdata, title, accept, cancel, NULL, 0)) { + return; + } if (window) { parent = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); @@ -532,9 +1026,11 @@ static void ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_ args->is_save = is_save; args->filters_str = filters_str; + args->nfilters = nfilters; args->default_file = default_location ? SDL_strdup(default_location) : NULL; args->parent = window; args->flags = flags; + args->allow_many = allow_many; args->callback = callback; args->userdata = userdata; args->title = title ? SDL_strdup(title) : NULL; @@ -571,6 +1067,7 @@ void ShowFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Windo } args->parent = window; + args->allow_many = allow_many; args->callback = callback; args->default_folder = default_location ? SDL_strdup(default_location) : NULL; args->userdata = userdata; diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index c69956e8b0..0627fe9ed7 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1253,6 +1253,8 @@ SDL3_0.0.0 { SDL_PutAudioStreamPlanarData; SDL_GetEventDescription; SDL_PutAudioStreamDataNoCopy; + SDL_AddAtomicU32; + SDL_hid_get_properties; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_dlopennote.h b/src/dynapi/SDL_dynapi_dlopennote.h new file mode 100644 index 0000000000..8a2ec58ed1 --- /dev/null +++ b/src/dynapi/SDL_dynapi_dlopennote.h @@ -0,0 +1,106 @@ +/* + 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_dynapi_dlopennote_h +#define SDL_dynapi_dlopennote_h + +#define SDL_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED "required" +#define SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended" +#define SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED "suggested" + +#if defined(__ELF__) && defined(HAVE_DLOPEN_NOTES) + +#define SDL_ELF_NOTE_DLOPEN_VENDOR "FDO" +#define SDL_ELF_NOTE_DLOPEN_TYPE 0x407c0c0aU + +#define SDL_ELF_NOTE_INTERNAL2(json, variable_name) \ + __attribute__((aligned(4), used, section(".note.dlopen"))) \ + static const struct { \ + struct { \ + Uint32 n_namesz; \ + Uint32 n_descsz; \ + Uint32 n_type; \ + } nhdr; \ + char name[4]; \ + __attribute__((aligned(4))) char dlopen_json[sizeof(json)]; \ + } variable_name = { \ + { \ + sizeof(SDL_ELF_NOTE_DLOPEN_VENDOR), \ + sizeof(json), \ + SDL_ELF_NOTE_DLOPEN_TYPE \ + }, \ + SDL_ELF_NOTE_DLOPEN_VENDOR, \ + json \ + } + +#define SDL_ELF_NOTE_INTERNAL(json, variable_name) \ + SDL_ELF_NOTE_INTERNAL2(json, variable_name) + +#define SDL_SONAME_ARRAY1(N1) "[\"" N1 "\"]" +#define SDL_SONAME_ARRAY2(N1,N2) "[\"" N1 "\",\"" N2 "\"]" +#define SDL_SONAME_ARRAY3(N1,N2,N3) "[\"" N1 "\",\"" N2 "\",\"" N3 "\"]" +#define SDL_SONAME_ARRAY4(N1,N2,N3,N4) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\"]" +#define SDL_SONAME_ARRAY5(N1,N2,N3,N4,N5) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\"]" +#define SDL_SONAME_ARRAY6(N1,N2,N3,N4,N5,N6) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\",\"" N6 "\"]" +#define SDL_SONAME_ARRAY7(N1,N2,N3,N4,N5,N6,N7) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\",\"" N6 "\",\"" N7 "\"]" +#define SDL_SONAME_ARRAY8(N1,N2,N3,N4,N5,N6,N7,N8) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\",\"" N6 "\",\"" N7 "\",\"" N8 "\"]" +#define SDL_SONAME_ARRAY_GET(N1,N2,N3,N4,N5,N6,N7,N8,NAME,...) NAME +#define SDL_SONAME_ARRAY(...) \ + SDL_SONAME_ARRAY_GET(__VA_ARGS__, \ + SDL_SONAME_ARRAY8, \ + SDL_SONAME_ARRAY7, \ + SDL_SONAME_ARRAY6, \ + SDL_SONAME_ARRAY5, \ + SDL_SONAME_ARRAY4, \ + SDL_SONAME_ARRAY3, \ + SDL_SONAME_ARRAY2, \ + SDL_SONAME_ARRAY1 \ + )(__VA_ARGS__) + +// Create "unique" variable name using __LINE__, +// so creating elf notes on the same line is not supported +#define SDL_ELF_NOTE_JOIN2(A,B) A##B +#define SDL_ELF_NOTE_JOIN(A,B) SDL_ELF_NOTE_JOIN2(A,B) +#define SDL_ELF_NOTE_UNIQUE_NAME SDL_ELF_NOTE_JOIN(s_dlopen_note_, __LINE__) + +#define SDL_ELF_NOTE_DLOPEN(feature, description, priority, ...) \ + SDL_ELF_NOTE_INTERNAL( \ + "[{\"feature\":\"" feature \ + "\",\"description\":\"" description \ + "\",\"priority\":\"" priority \ + "\",\"soname\":" SDL_SONAME_ARRAY(__VA_ARGS__) "}]", \ + SDL_ELF_NOTE_UNIQUE_NAME) + +#else + +#if defined (__GNUC__) && __GNUC__ < 3 + +#define SDL_ELF_NOTE_DLOPEN + +#else + +#define SDL_ELF_NOTE_DLOPEN(...) + +#endif + +#endif + +#endif diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 61eb4b956d..c074941858 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1278,3 +1278,5 @@ #define SDL_PutAudioStreamPlanarData SDL_PutAudioStreamPlanarData_REAL #define SDL_GetEventDescription SDL_GetEventDescription_REAL #define SDL_PutAudioStreamDataNoCopy SDL_PutAudioStreamDataNoCopy_REAL +#define SDL_AddAtomicU32 SDL_AddAtomicU32_REAL +#define SDL_hid_get_properties SDL_hid_get_properties_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index d1e362cdff..9876a3de8d 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -288,7 +288,7 @@ SDL_DYNAPI_PROC(const char*,SDL_GetCameraDriver,(int a),(a),return) SDL_DYNAPI_PROC(bool,SDL_GetCameraFormat,(SDL_Camera *a, SDL_CameraSpec *b),(a,b),return) SDL_DYNAPI_PROC(SDL_CameraID,SDL_GetCameraID,(SDL_Camera *a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetCameraName,(SDL_CameraID a),(a),return) -SDL_DYNAPI_PROC(int,SDL_GetCameraPermissionState,(SDL_Camera *a),(a),return) +SDL_DYNAPI_PROC(SDL_CameraPermissionState,SDL_GetCameraPermissionState,(SDL_Camera *a),(a),return) SDL_DYNAPI_PROC(SDL_CameraPosition,SDL_GetCameraPosition,(SDL_CameraID a),(a),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetCameraProperties,(SDL_Camera *a),(a),return) SDL_DYNAPI_PROC(SDL_CameraSpec**,SDL_GetCameraSupportedFormats,(SDL_CameraID a, int *b),(a,b),return) @@ -1271,7 +1271,7 @@ SDL_DYNAPI_PROC(bool,SDL_SetRelativeMouseTransform,(SDL_MouseMotionTransformCall SDL_DYNAPI_PROC(bool,SDL_RenderTexture9GridTiled,(SDL_Renderer *a,SDL_Texture *b,const SDL_FRect *c,float d,float e,float f,float g,float h,const SDL_FRect *i,float j),(a,b,c,d,e,f,g,h,i,j),return) SDL_DYNAPI_PROC(bool,SDL_SetDefaultTextureScaleMode,(SDL_Renderer *a,SDL_ScaleMode b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_GetDefaultTextureScaleMode,(SDL_Renderer *a,SDL_ScaleMode *b),(a,b),return) -SDL_DYNAPI_PROC(SDL_GPURenderState*,SDL_CreateGPURenderState,(SDL_Renderer *a,SDL_GPURenderStateDesc *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_GPURenderState*,SDL_CreateGPURenderState,(SDL_Renderer *a,SDL_GPURenderStateCreateInfo *b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_SetGPURenderStateFragmentUniforms,(SDL_GPURenderState *a,Uint32 b,const void *c,Uint32 d),(a,b,c,d),return) SDL_DYNAPI_PROC(bool,SDL_SetRenderGPUState,(SDL_Renderer *a,SDL_GPURenderState *b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_DestroyGPURenderState,(SDL_GPURenderState *a),(a),) @@ -1286,3 +1286,5 @@ SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateGPURenderer,(SDL_Window *a,SDL_GPUShader SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamPlanarData,(SDL_AudioStream *a,const void * const*b,int c,int d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_GetEventDescription,(const SDL_Event *a,char *b,int c),(a,b,c),return) SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamDataNoCopy,(SDL_AudioStream *a,const void *b,int c,SDL_AudioStreamDataCompleteCallback d,void *e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(Uint32,SDL_AddAtomicU32,(SDL_AtomicU32 *a,int b),(a,b),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_hid_get_properties,(SDL_hid_device *a),(a),return) diff --git a/src/events/SDL_keymap.c b/src/events/SDL_keymap.c index 21d287ccaa..9960aa9d60 100644 --- a/src/events/SDL_keymap.c +++ b/src/events/SDL_keymap.c @@ -207,6 +207,10 @@ void SDL_DestroyKeymap(SDL_Keymap *keymap) return; } + if (!keymap->auto_release && keymap == SDL_GetCurrentKeymap(true)) { + SDL_SetKeymap(NULL, false); + } + SDL_DestroyHashTable(keymap->scancode_to_keycode); SDL_DestroyHashTable(keymap->keycode_to_scancode); SDL_free(keymap); diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c index 54611bf36d..170e4335b3 100644 --- a/src/events/SDL_mouse.c +++ b/src/events/SDL_mouse.c @@ -1168,6 +1168,12 @@ void SDL_QuitMouse(void) } SDL_free(SDL_mice); SDL_mice = NULL; + + if (mouse->internal) { + SDL_free(mouse->internal); + mouse->internal = NULL; + } + SDL_zerop(mouse); } bool SDL_SetRelativeMouseTransform(SDL_MouseMotionTransformCallback transform, void *userdata) diff --git a/src/filesystem/unix/SDL_sysfilesystem.c b/src/filesystem/unix/SDL_sysfilesystem.c index 751cc8a45a..add7f49515 100644 --- a/src/filesystem/unix/SDL_sysfilesystem.c +++ b/src/filesystem/unix/SDL_sysfilesystem.c @@ -78,7 +78,7 @@ static char *search_path_for_binary(const char *bin) char *envr; size_t alloc_size; char *exe = NULL; - char *start = envr; + char *start; char *ptr; if (!envr_real) { @@ -86,7 +86,7 @@ static char *search_path_for_binary(const char *bin) return NULL; } - envr = SDL_strdup(envr_real); + start = envr = SDL_strdup(envr_real); if (!envr) { return NULL; } diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c index d148c431bc..dfc5f8a26b 100644 --- a/src/gpu/SDL_gpu.c +++ b/src/gpu/SDL_gpu.c @@ -1779,6 +1779,8 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( commandBufferHeader->render_pass.num_color_targets = num_color_targets; if (depth_stencil_target_info != NULL) { commandBufferHeader->render_pass.depth_stencil_target = depth_stencil_target_info->texture; + } else { + commandBufferHeader->render_pass.depth_stencil_target = NULL; } } diff --git a/src/gpu/d3d12/SDL_gpu_d3d12.c b/src/gpu/d3d12/SDL_gpu_d3d12.c index 13a15132f5..c277ced36c 100644 --- a/src/gpu/d3d12/SDL_gpu_d3d12.c +++ b/src/gpu/d3d12/SDL_gpu_d3d12.c @@ -109,8 +109,8 @@ #define DXGI_GET_DEBUG_INTERFACE_FUNC "DXGIGetDebugInterface" #define D3D12_GET_DEBUG_INTERFACE_FUNC "D3D12GetDebugInterface" #define WINDOW_PROPERTY_DATA "SDL_GPUD3D12WindowPropertyData" -#define D3D_FEATURE_LEVEL_CHOICE D3D_FEATURE_LEVEL_11_1 -#define D3D_FEATURE_LEVEL_CHOICE_STR "11_1" +#define D3D_FEATURE_LEVEL_CHOICE D3D_FEATURE_LEVEL_11_0 +#define D3D_FEATURE_LEVEL_CHOICE_STR "11_0" #define MAX_ROOT_SIGNATURE_PARAMETERS 64 #define D3D12_FENCE_UNSIGNALED_VALUE 0 #define D3D12_FENCE_SIGNAL_VALUE 1 @@ -8347,6 +8347,7 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) IDXGIFactory4 *factory4; IDXGIFactory6 *factory6; IDXGIAdapter1 *adapter; + bool supports_64UAVs = false; // Early check to see if the app has _any_ D3D12 formats, if not we don't // have to fuss with loading D3D in the first place. @@ -8446,12 +8447,39 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) return false; } + SDL_COMPILE_TIME_ASSERT(featurelevel, D3D_FEATURE_LEVEL_CHOICE < D3D_FEATURE_LEVEL_11_1); + + // Check feature level 11_1 first, guarantees 64+ UAVs unlike 11_0 Tier1 res = D3D12CreateDeviceFunc( (IUnknown *)adapter, - D3D_FEATURE_LEVEL_CHOICE, + D3D_FEATURE_LEVEL_11_1, D3D_GUID(D3D_IID_ID3D12Device), (void **)&device); + if (SUCCEEDED(res)) { + supports_64UAVs = true; + } else { + res = D3D12CreateDeviceFunc( + (IUnknown *)adapter, + D3D_FEATURE_LEVEL_CHOICE, + D3D_GUID(D3D_IID_ID3D12Device), + (void **)&device); + + if (SUCCEEDED(res)) { + D3D12_FEATURE_DATA_D3D12_OPTIONS featureOptions; + SDL_zero(featureOptions); + + res = ID3D12Device_CheckFeatureSupport( + device, + D3D12_FEATURE_D3D12_OPTIONS, + &featureOptions, + sizeof(featureOptions)); + if (SUCCEEDED(res) && featureOptions.ResourceBindingTier >= D3D12_RESOURCE_BINDING_TIER_2) { + supports_64UAVs = true; + } + } + } + if (SUCCEEDED(res)) { D3D12_FEATURE_DATA_SHADER_MODEL shaderModel; shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_0; @@ -8473,6 +8501,11 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) SDL_UnloadObject(d3d12Dll); SDL_UnloadObject(dxgiDll); + if (!supports_64UAVs) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: Tier 2 Resource binding is not supported"); + return false; + } + if (!supports_dxil && !has_dxbc) { SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: DXIL is not supported and DXBC is not being provided"); return false; diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index a1e074802a..069c2e585f 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -1116,6 +1116,7 @@ struct VulkanRenderer VulkanExtensions supports; bool supportsDebugUtils; bool supportsColorspace; + bool supportsPhysicalDeviceProperties2; bool supportsFillModeNonSolid; bool supportsMultiDrawIndirect; @@ -6065,7 +6066,6 @@ static VkRenderPass VULKAN_INTERNAL_CreateRenderPass( colorAttachmentReferences[colorAttachmentReferenceCount].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachmentDescriptionCount += 1; - colorAttachmentReferenceCount += 1; if (colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE || colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { VulkanTextureContainer *resolveContainer = (VulkanTextureContainer *)colorTargetInfos[i].resolve_texture; @@ -6080,12 +6080,16 @@ static VkRenderPass VULKAN_INTERNAL_CreateRenderPass( attachmentDescriptions[attachmentDescriptionCount].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachmentDescriptions[attachmentDescriptionCount].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - resolveReferences[resolveReferenceCount].attachment = attachmentDescriptionCount; - resolveReferences[resolveReferenceCount].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + resolveReferences[colorAttachmentReferenceCount].attachment = attachmentDescriptionCount; + resolveReferences[colorAttachmentReferenceCount].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachmentDescriptionCount += 1; resolveReferenceCount += 1; + } else { + resolveReferences[colorAttachmentReferenceCount].attachment = VK_ATTACHMENT_UNUSED; } + + colorAttachmentReferenceCount += 1; } subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; @@ -9405,6 +9409,8 @@ static bool VULKAN_INTERNAL_AllocateCommandBuffer( commandBuffer->usedUniformBuffers = SDL_malloc( commandBuffer->usedUniformBufferCapacity * sizeof(VulkanUniformBuffer *)); + commandBuffer->swapchainRequested = false; + // Pool it! vulkanCommandPool->inactiveCommandBuffers[vulkanCommandPool->inactiveCommandBufferCount] = commandBuffer; @@ -11031,7 +11037,8 @@ static Uint8 VULKAN_INTERNAL_CheckInstanceExtensions( const char **requiredExtensions, Uint32 requiredExtensionsLength, bool *supportsDebugUtils, - bool *supportsColorspace) + bool *supportsColorspace, + bool *supportsPhysicalDeviceProperties2) { Uint32 extensionCount, i; VkExtensionProperties *availableExtensions; @@ -11070,6 +11077,12 @@ static Uint8 VULKAN_INTERNAL_CheckInstanceExtensions( availableExtensions, extensionCount); + // Only needed for KHR_driver_properties! + *supportsPhysicalDeviceProperties2 = SupportsInstanceExtension( + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, + availableExtensions, + extensionCount); + SDL_free(availableExtensions); return allExtensionsSupported; } @@ -11180,10 +11193,6 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) instanceExtensionCount + 4); SDL_memcpy((void *)instanceExtensionNames, originalInstanceExtensionNames, instanceExtensionCount * sizeof(const char *)); - // Core since 1.1 - instanceExtensionNames[instanceExtensionCount++] = - VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; - #ifdef SDL_PLATFORM_APPLE instanceExtensionNames[instanceExtensionCount++] = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; @@ -11194,7 +11203,8 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) instanceExtensionNames, instanceExtensionCount, &renderer->supportsDebugUtils, - &renderer->supportsColorspace)) { + &renderer->supportsColorspace, + &renderer->supportsPhysicalDeviceProperties2)) { SDL_stack_free((char *)instanceExtensionNames); SET_STRING_ERROR_AND_RETURN("Required Vulkan instance extensions not supported", false); } @@ -11216,6 +11226,12 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME; } + if (renderer->supportsPhysicalDeviceProperties2) { + // Append KHR_physical_device_properties2 extension + instanceExtensionNames[instanceExtensionCount++] = + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; + } + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pNext = NULL; createInfo.flags = createFlags; @@ -11252,13 +11268,14 @@ static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( VkPhysicalDevice physicalDevice, VulkanExtensions *physicalDeviceExtensions, Uint32 *queueFamilyIndex, - Uint8 *deviceRank) + Uint64 *deviceRank) { Uint32 queueFamilyCount, queueFamilyRank, queueFamilyBest; VkQueueFamilyProperties *queueProps; bool supportsPresent; VkPhysicalDeviceProperties deviceProperties; VkPhysicalDeviceFeatures deviceFeatures; + VkPhysicalDeviceMemoryProperties deviceMemory; Uint32 i; const Uint8 *devicePriority = renderer->preferLowPower ? DEVICE_PRIORITY_LOWPOWER : DEVICE_PRIORITY_HIGHPERFORMANCE; @@ -11270,14 +11287,20 @@ static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( renderer->vkGetPhysicalDeviceProperties( physicalDevice, &deviceProperties); - if (*deviceRank < devicePriority[deviceProperties.deviceType]) { + + /* Apply a large bias on the devicePriority so that we always respect the order in the priority arrays. + * We also rank by e.g. VRAM which should have less influence than the device type. + */ + Uint64 devicePriorityValue = devicePriority[deviceProperties.deviceType] * 1000000; + + if (*deviceRank < devicePriorityValue) { /* This device outranks the best device we've found so far! * This includes a dedicated GPU that has less features than an * integrated GPU, because this is a freak case that is almost * never intentionally desired by the end user */ - *deviceRank = devicePriority[deviceProperties.deviceType]; - } else if (*deviceRank > devicePriority[deviceProperties.deviceType]) { + *deviceRank = devicePriorityValue; + } else if (*deviceRank > devicePriorityValue) { /* Device is outranked by a previous device, don't even try to * run a query and reset the rank to avoid overwrites */ @@ -11380,6 +11403,30 @@ static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( return 0; } + /* If we prefer high performance, sum up all device local memory (rounded to megabytes) + * to deviceRank. In the niche case of someone having multiple dedicated GPUs in the same + * system, this theoretically picks the most powerful one (or at least the one with the + * most memory!) + * + * We do this *after* discarding all non suitable devices, which means if this computer + * has multiple dedicated GPUs that all meet our criteria, *and* the user asked for high + * performance, then we always pick the GPU with more VRAM. + */ + if (!renderer->preferLowPower) { + renderer->vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemory); + Uint64 videoMemory = 0; + for (i = 0; i < deviceMemory.memoryHeapCount; i++) { + VkMemoryHeap heap = deviceMemory.memoryHeaps[i]; + if (heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) { + videoMemory += heap.size; + } + } + // Round it to megabytes (as per the vulkan spec videoMemory is in bytes) + Uint64 videoMemoryRounded = videoMemory / 1024 / 1024; + *deviceRank += videoMemoryRounded; + } + + // FIXME: Need better structure for checking vs storing swapchain support details return 1; } @@ -11392,7 +11439,7 @@ static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer) Uint32 i, physicalDeviceCount; Sint32 suitableIndex; Uint32 queueFamilyIndex, suitableQueueFamilyIndex; - Uint8 deviceRank, highestRank; + Uint64 deviceRank, highestRank; vulkanResult = renderer->vkEnumeratePhysicalDevices( renderer->instance, diff --git a/src/hidapi/SDL_hidapi.c b/src/hidapi/SDL_hidapi.c index 41e24ec8a0..275a12553a 100644 --- a/src/hidapi/SDL_hidapi.c +++ b/src/hidapi/SDL_hidapi.c @@ -40,6 +40,15 @@ #ifndef SDL_HIDAPI_DISABLED +#ifdef SDL_LIBUSB_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "hidabi-libusb", + "Support for joysticks through libusb", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_LIBUSB_DYNAMIC +); +#endif + #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) #include "../core/windows/SDL_windows.h" #endif @@ -689,96 +698,40 @@ typedef struct DRIVER_hid_device_ DRIVER_hid_device; #ifdef HAVE_LIBUSB // libusb HIDAPI Implementation -// Include this now, for our dynamically-loaded libusb context -#include +#include "../misc/SDL_libusb.h" -static struct -{ - SDL_SharedObject *libhandle; +static SDL_LibUSBContext *libusb_ctx; - /* *INDENT-OFF* */ // clang-format off - int (LIBUSB_CALL *init)(libusb_context **ctx); - void (LIBUSB_CALL *exit)(libusb_context *ctx); - ssize_t (LIBUSB_CALL *get_device_list)(libusb_context *ctx, libusb_device ***list); - void (LIBUSB_CALL *free_device_list)(libusb_device **list, int unref_devices); - int (LIBUSB_CALL *get_device_descriptor)(libusb_device *dev, struct libusb_device_descriptor *desc); - int (LIBUSB_CALL *get_active_config_descriptor)(libusb_device *dev, struct libusb_config_descriptor **config); - int (LIBUSB_CALL *get_config_descriptor)( - libusb_device *dev, - uint8_t config_index, - struct libusb_config_descriptor **config - ); - void (LIBUSB_CALL *free_config_descriptor)(struct libusb_config_descriptor *config); - uint8_t (LIBUSB_CALL *get_bus_number)(libusb_device *dev); - int (LIBUSB_CALL *get_port_numbers)(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len); - uint8_t (LIBUSB_CALL *get_device_address)(libusb_device *dev); - int (LIBUSB_CALL *open)(libusb_device *dev, libusb_device_handle **dev_handle); - void (LIBUSB_CALL *close)(libusb_device_handle *dev_handle); - libusb_device *(LIBUSB_CALL *get_device)(libusb_device_handle *dev_handle); - int (LIBUSB_CALL *claim_interface)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *release_interface)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *kernel_driver_active)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *detach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *attach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); - int (LIBUSB_CALL *set_interface_alt_setting)(libusb_device_handle *dev, int interface_number, int alternate_setting); - struct libusb_transfer * (LIBUSB_CALL *alloc_transfer)(int iso_packets); - int (LIBUSB_CALL *submit_transfer)(struct libusb_transfer *transfer); - int (LIBUSB_CALL *cancel_transfer)(struct libusb_transfer *transfer); - void (LIBUSB_CALL *free_transfer)(struct libusb_transfer *transfer); - int (LIBUSB_CALL *control_transfer)( - libusb_device_handle *dev_handle, - uint8_t request_type, - uint8_t bRequest, - uint16_t wValue, - uint16_t wIndex, - unsigned char *data, - uint16_t wLength, - unsigned int timeout - ); - int (LIBUSB_CALL *interrupt_transfer)( - libusb_device_handle *dev_handle, - unsigned char endpoint, - unsigned char *data, - int length, - int *actual_length, - unsigned int timeout - ); - int (LIBUSB_CALL *handle_events)(libusb_context *ctx); - int (LIBUSB_CALL *handle_events_completed)(libusb_context *ctx, int *completed); - const char * (LIBUSB_CALL *error_name)(int errcode); -/* *INDENT-ON* */ // clang-format on - -} libusb_ctx; - -#define libusb_init libusb_ctx.init -#define libusb_exit libusb_ctx.exit -#define libusb_get_device_list libusb_ctx.get_device_list -#define libusb_free_device_list libusb_ctx.free_device_list -#define libusb_get_device_descriptor libusb_ctx.get_device_descriptor -#define libusb_get_active_config_descriptor libusb_ctx.get_active_config_descriptor -#define libusb_get_config_descriptor libusb_ctx.get_config_descriptor -#define libusb_free_config_descriptor libusb_ctx.free_config_descriptor -#define libusb_get_bus_number libusb_ctx.get_bus_number -#define libusb_get_port_numbers libusb_ctx.get_port_numbers -#define libusb_get_device_address libusb_ctx.get_device_address -#define libusb_open libusb_ctx.open -#define libusb_close libusb_ctx.close -#define libusb_get_device libusb_ctx.get_device -#define libusb_claim_interface libusb_ctx.claim_interface -#define libusb_release_interface libusb_ctx.release_interface -#define libusb_kernel_driver_active libusb_ctx.kernel_driver_active -#define libusb_detach_kernel_driver libusb_ctx.detach_kernel_driver -#define libusb_attach_kernel_driver libusb_ctx.attach_kernel_driver -#define libusb_set_interface_alt_setting libusb_ctx.set_interface_alt_setting -#define libusb_alloc_transfer libusb_ctx.alloc_transfer -#define libusb_submit_transfer libusb_ctx.submit_transfer -#define libusb_cancel_transfer libusb_ctx.cancel_transfer -#define libusb_free_transfer libusb_ctx.free_transfer -#define libusb_control_transfer libusb_ctx.control_transfer -#define libusb_interrupt_transfer libusb_ctx.interrupt_transfer -#define libusb_handle_events libusb_ctx.handle_events -#define libusb_handle_events_completed libusb_ctx.handle_events_completed -#define libusb_error_name libusb_ctx.error_name +#define libusb_init libusb_ctx->init +#define libusb_exit libusb_ctx->exit +#define libusb_get_device_list libusb_ctx->get_device_list +#define libusb_free_device_list libusb_ctx->free_device_list +#define libusb_get_device_descriptor libusb_ctx->get_device_descriptor +#define libusb_get_active_config_descriptor libusb_ctx->get_active_config_descriptor +#define libusb_get_config_descriptor libusb_ctx->get_config_descriptor +#define libusb_free_config_descriptor libusb_ctx->free_config_descriptor +#define libusb_get_bus_number libusb_ctx->get_bus_number +#define libusb_get_port_numbers libusb_ctx->get_port_numbers +#define libusb_get_device_address libusb_ctx->get_device_address +#define libusb_open libusb_ctx->open +#define libusb_close libusb_ctx->close +#define libusb_get_device libusb_ctx->get_device +#define libusb_claim_interface libusb_ctx->claim_interface +#define libusb_release_interface libusb_ctx->release_interface +#define libusb_kernel_driver_active libusb_ctx->kernel_driver_active +#define libusb_detach_kernel_driver libusb_ctx->detach_kernel_driver +#define libusb_attach_kernel_driver libusb_ctx->attach_kernel_driver +#define libusb_set_interface_alt_setting libusb_ctx->set_interface_alt_setting +#define libusb_alloc_transfer libusb_ctx->alloc_transfer +#define libusb_submit_transfer libusb_ctx->submit_transfer +#define libusb_cancel_transfer libusb_ctx->cancel_transfer +#define libusb_free_transfer libusb_ctx->free_transfer +#define libusb_control_transfer libusb_ctx->control_transfer +#define libusb_interrupt_transfer libusb_ctx->interrupt_transfer +#define libusb_bulk_transfer libusb_ctx->bulk_transfer +#define libusb_handle_events libusb_ctx->handle_events +#define libusb_handle_events_completed libusb_ctx->handle_events_completed +#define libusb_error_name libusb_ctx->error_name struct LIBUSB_hid_device_; typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; @@ -843,6 +796,7 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; #undef libusb_free_transfer #undef libusb_control_transfer #undef libusb_interrupt_transfer +#undef libusb_bulk_transfer #undef libusb_handle_events #undef libusb_handle_events_completed #undef libusb_error_name @@ -889,7 +843,11 @@ static const struct { Uint16 vendor; Uint16 product; } SDL_libusb_whitelist[] = { - { 0x057e, 0x0337 } // Nintendo WUP-028, Wii U/Switch GameCube Adapter + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER }, + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER }, + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT }, + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT }, + { USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH2_PRO }, }; static bool IsInWhitelist(Uint16 vendor, Uint16 product) @@ -1004,13 +962,14 @@ struct SDL_hid_device void *device; const struct hidapi_backend *backend; SDL_hid_device_info info; + SDL_PropertiesID props; }; #if defined(HAVE_PLATFORM_BACKEND) || defined(HAVE_DRIVER_BACKEND) || defined(HAVE_LIBUSB) static SDL_hid_device *CreateHIDDeviceWrapper(void *device, const struct hidapi_backend *backend) { - SDL_hid_device *wrapper = (SDL_hid_device *)SDL_malloc(sizeof(*wrapper)); + SDL_hid_device *wrapper = (SDL_hid_device *)SDL_calloc(1, sizeof(*wrapper)); SDL_SetObjectValid(wrapper, SDL_OBJECT_TYPE_HIDAPI_DEVICE, true); wrapper->device = device; wrapper->backend = backend; @@ -1167,70 +1126,15 @@ int SDL_hid_init(void) if (!SDL_GetHintBoolean(SDL_HINT_HIDAPI_LIBUSB, true)) { SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "libusb disabled with SDL_HINT_HIDAPI_LIBUSB"); - libusb_ctx.libhandle = NULL; } else { ++attempts; -#ifdef SDL_LIBUSB_DYNAMIC - libusb_ctx.libhandle = SDL_LoadObject(SDL_LIBUSB_DYNAMIC); -#else - libusb_ctx.libhandle = (void *)1; -#endif - if (libusb_ctx.libhandle != NULL) { - bool loaded = true; -#ifdef SDL_LIBUSB_DYNAMIC -#define LOAD_LIBUSB_SYMBOL(type, func) \ - if ((libusb_ctx.func = (type)SDL_LoadFunction(libusb_ctx.libhandle, "libusb_" #func)) == NULL) { \ - loaded = false; \ - } -#else -#define LOAD_LIBUSB_SYMBOL(type, func) \ - libusb_ctx.func = libusb_##func; -#endif - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context **), init) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_context *), exit) - LOAD_LIBUSB_SYMBOL(ssize_t (LIBUSB_CALL *)(libusb_context *, libusb_device ***), get_device_list) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_device **, int), free_device_list) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, struct libusb_device_descriptor *), get_device_descriptor) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, struct libusb_config_descriptor **), get_active_config_descriptor) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, uint8_t, struct libusb_config_descriptor **), get_config_descriptor) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_config_descriptor *), free_config_descriptor) - LOAD_LIBUSB_SYMBOL(uint8_t (LIBUSB_CALL *)(libusb_device *), get_bus_number) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len), get_port_numbers) - LOAD_LIBUSB_SYMBOL(uint8_t (LIBUSB_CALL *)(libusb_device *), get_device_address) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, libusb_device_handle **), open) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_device_handle *), close) - LOAD_LIBUSB_SYMBOL(libusb_device * (LIBUSB_CALL *)(libusb_device_handle *dev_handle), get_device) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), claim_interface) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), release_interface) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), kernel_driver_active) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), detach_kernel_driver) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), attach_kernel_driver) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int, int), set_interface_alt_setting) - LOAD_LIBUSB_SYMBOL(struct libusb_transfer * (LIBUSB_CALL *)(int), alloc_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(struct libusb_transfer *), submit_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(struct libusb_transfer *), cancel_transfer) - LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_transfer *), free_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, uint8_t, uint8_t, uint16_t, uint16_t, unsigned char *, uint16_t, unsigned int), control_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), interrupt_transfer) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *), handle_events) - LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *, int *), handle_events_completed) - LOAD_LIBUSB_SYMBOL(const char * (LIBUSB_CALL *)(int), error_name) -#undef LOAD_LIBUSB_SYMBOL - - if (!loaded) { -#ifdef SDL_LIBUSB_DYNAMIC - SDL_UnloadObject(libusb_ctx.libhandle); -#endif - libusb_ctx.libhandle = NULL; - // SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, SDL_LIBUSB_DYNAMIC " found but could not load function"); - } else if (LIBUSB_hid_init() < 0) { -#ifdef SDL_LIBUSB_DYNAMIC - SDL_UnloadObject(libusb_ctx.libhandle); -#endif - libusb_ctx.libhandle = NULL; - } else { - ++success; - } + if (!SDL_InitLibUSB(&libusb_ctx)) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Couldn't load libusb"); + } else if (LIBUSB_hid_init() < 0) { + SDL_QuitLibUSB(); + libusb_ctx = NULL; + } else { + ++success; } } #endif // HAVE_LIBUSB @@ -1284,12 +1188,10 @@ int SDL_hid_exit(void) #endif // HAVE_PLATFORM_BACKEND #ifdef HAVE_LIBUSB - if (libusb_ctx.libhandle) { + if (libusb_ctx) { result |= LIBUSB_hid_exit(); -#ifdef SDL_LIBUSB_DYNAMIC - SDL_UnloadObject(libusb_ctx.libhandle); -#endif - libusb_ctx.libhandle = NULL; + SDL_QuitLibUSB(); + libusb_ctx = NULL; } #endif // HAVE_LIBUSB @@ -1430,7 +1332,7 @@ struct SDL_hid_device_info *SDL_hid_enumerate(unsigned short vendor_id, unsigned #endif #ifdef HAVE_LIBUSB - if (libusb_ctx.libhandle) { + if (libusb_ctx) { usb_devs = LIBUSB_hid_enumerate(vendor_id, product_id); if (use_libusb_whitelist) { @@ -1531,10 +1433,12 @@ SDL_hid_device *SDL_hid_open(unsigned short vendor_id, unsigned short product_id #endif // HAVE_DRIVER_BACKEND #ifdef HAVE_LIBUSB - if (libusb_ctx.libhandle != NULL) { + if (libusb_ctx) { pDevice = LIBUSB_hid_open(vendor_id, product_id, serial_number); if (pDevice != NULL) { - return CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + SDL_hid_device *dev = CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + SDL_SetPointerProperty(SDL_hid_get_properties(dev), SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER, ((LIBUSB_hid_device *)pDevice)->device_handle); + return dev; } } #endif // HAVE_LIBUSB @@ -1570,10 +1474,12 @@ SDL_hid_device *SDL_hid_open_path(const char *path) #endif // HAVE_DRIVER_BACKEND #ifdef HAVE_LIBUSB - if (libusb_ctx.libhandle != NULL) { + if (libusb_ctx) { pDevice = LIBUSB_hid_open_path(path); if (pDevice != NULL) { - return CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + SDL_hid_device *dev = CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + SDL_SetPointerProperty(SDL_hid_get_properties(dev), SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER, ((LIBUSB_hid_device *)pDevice)->device_handle); + return dev; } } #endif // HAVE_LIBUSB @@ -1583,6 +1489,16 @@ SDL_hid_device *SDL_hid_open_path(const char *path) return NULL; } +SDL_PropertiesID SDL_hid_get_properties(SDL_hid_device *device) +{ + CHECK_DEVICE_MAGIC(device, 0); + + if (!device->props) { + device->props = SDL_CreateProperties(); + } + return device->props; +} + int SDL_hid_write(SDL_hid_device *device, const unsigned char *data, size_t length) { CHECK_DEVICE_MAGIC(device, -1); @@ -1637,6 +1553,7 @@ int SDL_hid_close(SDL_hid_device *device) CHECK_DEVICE_MAGIC(device, -1); device->backend->hid_close(device->device); + SDL_DestroyProperties(device->props); DeleteHIDDeviceWrapper(device); return 0; } @@ -1711,14 +1628,14 @@ void SDL_EnableGameCubeAdaptors(void) ssize_t i, num_devs; int kernel_detached = 0; - if (libusb_ctx.libhandle == NULL) { + if (!libusb_ctx) { return; } - if (libusb_ctx.init(&context) == 0) { - num_devs = libusb_ctx.get_device_list(context, &devs); + if (libusb_ctx->init(&context) == 0) { + num_devs = libusb_ctx->get_device_list(context, &devs); for (i = 0; i < num_devs; ++i) { - if (libusb_ctx.get_device_descriptor(devs[i], &desc) != 0) { + if (libusb_ctx->get_device_descriptor(devs[i], &desc) != 0) { continue; } @@ -1726,31 +1643,31 @@ void SDL_EnableGameCubeAdaptors(void) continue; } - if (libusb_ctx.open(devs[i], &handle) != 0) { + if (libusb_ctx->open(devs[i], &handle) != 0) { continue; } - if (libusb_ctx.kernel_driver_active(handle, 0)) { - if (libusb_ctx.detach_kernel_driver(handle, 0) == 0) { + if (libusb_ctx->kernel_driver_active(handle, 0)) { + if (libusb_ctx->detach_kernel_driver(handle, 0) == 0) { kernel_detached = 1; } } - if (libusb_ctx.claim_interface(handle, 0) == 0) { - libusb_ctx.control_transfer(handle, 0x21, 11, 0x0001, 0, NULL, 0, 1000); - libusb_ctx.release_interface(handle, 0); + if (libusb_ctx->claim_interface(handle, 0) == 0) { + libusb_ctx->control_transfer(handle, 0x21, 11, 0x0001, 0, NULL, 0, 1000); + libusb_ctx->release_interface(handle, 0); } if (kernel_detached) { - libusb_ctx.attach_kernel_driver(handle, 0); + libusb_ctx->attach_kernel_driver(handle, 0); } - libusb_ctx.close(handle); + libusb_ctx->close(handle); } - libusb_ctx.free_device_list(devs, 1); + libusb_ctx->free_device_list(devs, 1); - libusb_ctx.exit(context); + libusb_ctx->exit(context); } #endif // HAVE_LIBUSB } diff --git a/src/hidapi/libusb/hid.c b/src/hidapi/libusb/hid.c index 0c4fc660ba..3020cfb3f4 100644 --- a/src/hidapi/libusb/hid.c +++ b/src/hidapi/libusb/hid.c @@ -260,19 +260,19 @@ static int get_usage(uint8_t *report_descriptor, size_t size, } if (key_cmd == 0x4) { - *usage_page = get_bytes(report_descriptor, size, data_len, i); + *usage_page = (unsigned short)get_bytes(report_descriptor, size, data_len, i); usage_page_found = 1; //printf("Usage Page: %x\n", (uint32_t)*usage_page); } if (key_cmd == 0x8) { if (data_len == 4) { /* Usages 5.5 / Usage Page 6.2.2.7 */ - *usage_page = get_bytes(report_descriptor, size, 2, i + 2); + *usage_page = (unsigned short)get_bytes(report_descriptor, size, 2, i + 2); usage_page_found = 1; - *usage = get_bytes(report_descriptor, size, 2, i); + *usage = (unsigned short)get_bytes(report_descriptor, size, 2, i); usage_found = 1; } else { - *usage = get_bytes(report_descriptor, size, data_len, i); + *usage = (unsigned short)get_bytes(report_descriptor, size, data_len, i); usage_found = 1; } //printf("Usage: %x\n", (uint32_t)*usage); @@ -640,7 +640,7 @@ static int hid_get_report_descriptor_libusb(libusb_device_handle *handle, int in /* Get the HID Report Descriptor. See USB HID Specification, section 7.1.1 */ - int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8), interface_num, tmp, expected_report_descriptor_size, 5000); + int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8), (uint16_t)interface_num, tmp, expected_report_descriptor_size, 5000); if (res >= 0) { if (res > (int)buf_size) res = (int)buf_size; @@ -1195,7 +1195,7 @@ static void *read_thread(void *param) dev->transfer = libusb_alloc_transfer(0); libusb_fill_interrupt_transfer(dev->transfer, dev->device_handle, - dev->input_endpoint, + (unsigned char)dev->input_endpoint, buf, (int) length, read_callback, @@ -1614,8 +1614,8 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t res = libusb_control_transfer(dev->device_handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, 0x09/*HID Set_Report*/, - (2/*HID output*/ << 8) | report_number, - dev->interface, + (uint16_t)((2/*HID output*/ << 8) | report_number), + (uint16_t)dev->interface, (unsigned char *)data, (uint16_t)length, 1000/*timeout millis*/); @@ -1631,7 +1631,7 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t /* Use the interrupt out endpoint */ int actual_length; res = libusb_interrupt_transfer(dev->device_handle, - dev->output_endpoint, + (unsigned char)dev->output_endpoint, (unsigned char*)data, (int) length, &actual_length, 1000); @@ -1780,8 +1780,8 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char res = libusb_control_transfer(dev->device_handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, 0x09/*HID set_report*/, - (3/*HID feature*/ << 8) | report_number, - dev->interface, + (uint16_t)((3/*HID feature*/ << 8) | report_number), + (uint16_t)dev->interface, (unsigned char *)data, (uint16_t)length, 1000/*timeout millis*/); @@ -1811,8 +1811,8 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, res = libusb_control_transfer(dev->device_handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN, 0x01/*HID get_report*/, - (3/*HID feature*/ << 8) | report_number, - dev->interface, + (uint16_t)((3/*HID feature*/ << 8) | report_number), + (uint16_t)dev->interface, (unsigned char *)data, (uint16_t)length, 1000/*timeout millis*/); @@ -1841,8 +1841,8 @@ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned c res = libusb_control_transfer(dev->device_handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN, 0x01/*HID get_report*/, - (1/*HID Input*/ << 8) | report_number, - dev->interface, + (uint16_t)((1/*HID Input*/ << 8) | report_number), + (uint16_t)dev->interface, (unsigned char *)data, (uint16_t)length, 1000/*timeout millis*/); @@ -1934,7 +1934,7 @@ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index { wchar_t *str; - str = get_usb_string(dev->device_handle, string_index); + str = get_usb_string(dev->device_handle, (uint8_t)string_index); if (str) { wcsncpy(string, str, maxlen); string[maxlen-1] = L'\0'; @@ -2122,7 +2122,7 @@ uint16_t get_usb_code_for_current_locale(void) /* Chop off the encoding part, and make it lower case. */ ptr = search_string; while (*ptr) { - *ptr = tolower(*ptr); + *ptr = (char)tolower(*ptr); if (*ptr == '.') { *ptr = '\0'; break; @@ -2143,7 +2143,7 @@ uint16_t get_usb_code_for_current_locale(void) /* Chop off the variant. Chop it off at the '_'. */ ptr = search_string; while (*ptr) { - *ptr = tolower(*ptr); + *ptr = (char)tolower(*ptr); if (*ptr == '_') { *ptr = '\0'; break; diff --git a/src/io/SDL_iostream.c b/src/io/SDL_iostream.c index 7c956baf5f..2c1544144f 100644 --- a/src/io/SDL_iostream.c +++ b/src/io/SDL_iostream.c @@ -220,15 +220,17 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size, switch (error) { case ERROR_BROKEN_PIPE: case ERROR_HANDLE_EOF: + *status = SDL_IO_STATUS_EOF; break; case ERROR_NO_DATA: *status = SDL_IO_STATUS_NOT_READY; break; default: + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error reading from datastream"); break; } - return 0; + return 0; // !!! FIXME: this should return the bytes read from any readahead we finished out before this (the `iodata->left > 0` code above). In that case, fail on the next read. } read_ahead = SDL_min(total_need, bytes); SDL_memcpy(ptr, iodata->data, read_ahead); @@ -241,15 +243,17 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size, switch (error) { case ERROR_BROKEN_PIPE: case ERROR_HANDLE_EOF: + *status = SDL_IO_STATUS_EOF; break; case ERROR_NO_DATA: *status = SDL_IO_STATUS_NOT_READY; break; default: + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error reading from datastream"); break; } - return 0; + return 0; // !!! FIXME: this should return the bytes read from any readahead we finished out before this (the `iodata->left > 0` code above). In that case, fail on the next read. } total_read += bytes; } @@ -263,6 +267,7 @@ static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t if (iodata->left) { if (!SetFilePointer(iodata->h, -(LONG)iodata->left, NULL, FILE_CURRENT)) { + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error seeking in datastream"); return 0; } @@ -274,16 +279,17 @@ static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t LARGE_INTEGER windowsoffset; windowsoffset.QuadPart = 0; if (!SetFilePointerEx(iodata->h, windowsoffset, &windowsoffset, FILE_END)) { + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error seeking in datastream"); return 0; } } if (!WriteFile(iodata->h, ptr, (DWORD)size, &bytes, NULL)) { + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error writing to datastream"); return 0; - } - if (bytes == 0 && size > 0) { + } else if (bytes == 0 && size > 0) { *status = SDL_IO_STATUS_NOT_READY; } return bytes; @@ -421,9 +427,12 @@ static size_t SDLCALL fd_read(void *userdata, void *ptr, size_t size, SDL_IOStat if (errno == EAGAIN) { *status = SDL_IO_STATUS_NOT_READY; } else { + *status = SDL_IO_STATUS_ERROR; SDL_SetError("Error reading from datastream: %s", strerror(errno)); } bytes = 0; + } else if (bytes < size) { + *status = SDL_IO_STATUS_EOF; } return (size_t)bytes; } @@ -440,6 +449,7 @@ static size_t SDLCALL fd_write(void *userdata, const void *ptr, size_t size, SDL if (errno == EAGAIN) { *status = SDL_IO_STATUS_NOT_READY; } else { + *status = SDL_IO_STATUS_ERROR; SDL_SetError("Error writing to datastream: %s", strerror(errno)); } bytes = 0; @@ -606,12 +616,18 @@ static size_t SDLCALL stdio_read(void *userdata, void *ptr, size_t size, SDL_IOS { IOStreamStdioData *iodata = (IOStreamStdioData *) userdata; const size_t bytes = fread(ptr, 1, size, iodata->fp); - if (bytes == 0 && ferror(iodata->fp)) { - if (errno == EAGAIN) { - *status = SDL_IO_STATUS_NOT_READY; - clearerr(iodata->fp); + if (bytes < size) { + if (ferror(iodata->fp)) { + if (errno == EAGAIN) { + *status = SDL_IO_STATUS_NOT_READY; + clearerr(iodata->fp); + } else { + *status = SDL_IO_STATUS_ERROR; + SDL_SetError("Error reading from datastream: %s", strerror(errno)); + } } else { - SDL_SetError("Error reading from datastream: %s", strerror(errno)); + SDL_assert(feof(iodata->fp)); + *status = SDL_IO_STATUS_EOF; } } return bytes; @@ -626,6 +642,7 @@ static size_t SDLCALL stdio_write(void *userdata, const void *ptr, size_t size, *status = SDL_IO_STATUS_NOT_READY; clearerr(iodata->fp); } else { + *status = SDL_IO_STATUS_ERROR; SDL_SetError("Error writing to datastream: %s", strerror(errno)); } } @@ -769,13 +786,22 @@ static size_t mem_io(void *userdata, void *dst, const void *src, size_t size) static size_t SDLCALL mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) { IOStreamMemData *iodata = (IOStreamMemData *) userdata; - return mem_io(userdata, ptr, iodata->here, size); + const size_t retval = mem_io(userdata, ptr, iodata->here, size); + if ((retval < size) && (iodata->stop == iodata->here)) { + *status = SDL_IO_STATUS_EOF; + } + return retval; } static size_t SDLCALL mem_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status) { IOStreamMemData *iodata = (IOStreamMemData *) userdata; - return mem_io(userdata, iodata->here, ptr, size); + const size_t retval = mem_io(userdata, iodata->here, ptr, size); + if ((retval < size) && (iodata->stop == iodata->here)) { + SDL_SetError("Memory buffer is full"); + *status = SDL_IO_STATUS_ERROR; + } + return retval; } static bool SDLCALL mem_close(void *userdata) @@ -1027,7 +1053,11 @@ static Sint64 SDLCALL dynamic_mem_seek(void *userdata, Sint64 offset, SDL_IOWhen static size_t SDLCALL dynamic_mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) { IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; - return mem_io(&iodata->data, ptr, iodata->data.here, size); + const size_t retval = mem_io(&iodata->data, ptr, iodata->data.here, size); + if ((retval < size) && (iodata->data.stop == iodata->data.here)) { + *status = SDL_IO_STATUS_EOF; + } + return retval; } static bool dynamic_mem_realloc(IOStreamDynamicMemData *iodata, size_t size) @@ -1060,12 +1090,15 @@ static size_t SDLCALL dynamic_mem_write(void *userdata, const void *ptr, size_t if (size > (size_t)(iodata->data.stop - iodata->data.here)) { if (size > (size_t)(iodata->end - iodata->data.here)) { if (!dynamic_mem_realloc(iodata, size)) { + *status = SDL_IO_STATUS_ERROR; return 0; } } iodata->data.stop = iodata->data.here + size; } - return mem_io(&iodata->data, iodata->data.here, ptr, size); + const size_t retval = mem_io(&iodata->data, iodata->data.here, ptr, size); + SDL_assert(retval == size); // we should have allocated enough to cover this! + return retval; } static bool SDLCALL dynamic_mem_close(void *userdata) @@ -1333,8 +1366,6 @@ Sint64 SDL_TellIO(SDL_IOStream *context) size_t SDL_ReadIO(SDL_IOStream *context, void *ptr, size_t size) { - size_t bytes; - if (!context) { SDL_InvalidParamError("context"); return 0; @@ -1342,30 +1373,18 @@ size_t SDL_ReadIO(SDL_IOStream *context, void *ptr, size_t size) context->status = SDL_IO_STATUS_WRITEONLY; SDL_Unsupported(); return 0; + } else if (size == 0) { + return 0; // context->status doesn't change for this. } context->status = SDL_IO_STATUS_READY; SDL_ClearError(); - if (size == 0) { - return 0; - } - - bytes = context->iface.read(context->userdata, ptr, size, &context->status); - if (bytes == 0 && context->status == SDL_IO_STATUS_READY) { - if (*SDL_GetError()) { - context->status = SDL_IO_STATUS_ERROR; - } else { - context->status = SDL_IO_STATUS_EOF; - } - } - return bytes; + return context->iface.read(context->userdata, ptr, size, &context->status); } size_t SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size) { - size_t bytes; - if (!context) { SDL_InvalidParamError("context"); return 0; @@ -1373,20 +1392,14 @@ size_t SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size) context->status = SDL_IO_STATUS_READONLY; SDL_Unsupported(); return 0; + } else if (size == 0) { + return 0; // context->status doesn't change for this. } context->status = SDL_IO_STATUS_READY; SDL_ClearError(); - if (size == 0) { - return 0; - } - - bytes = context->iface.write(context->userdata, ptr, size, &context->status); - if ((bytes == 0) && (context->status == SDL_IO_STATUS_READY)) { - context->status = SDL_IO_STATUS_ERROR; - } - return bytes; + return context->iface.write(context->userdata, ptr, size, &context->status); } size_t SDL_IOprintf(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) diff --git a/src/io/io_uring/SDL_asyncio_liburing.c b/src/io/io_uring/SDL_asyncio_liburing.c index 4aef5f4b12..c1d9e5aeb8 100644 --- a/src/io/io_uring/SDL_asyncio_liburing.c +++ b/src/io/io_uring/SDL_asyncio_liburing.c @@ -44,9 +44,17 @@ static bool (*AsyncIOFromFile)(const char *file, const char *mode, SDL_AsyncIO * // we never link directly to liburing. // (this says "-ffi" which sounds like a scripting language binding thing, but the non-ffi version // is static-inline code we can't lookup with dlsym. This is by design.) -static const char *liburing_library = "liburing-ffi.so.2"; +#define SDL_DRIVER_LIBURING_DYNAMIC "liburing-ffi.so.2" +static const char *liburing_library = SDL_DRIVER_LIBURING_DYNAMIC; static void *liburing_handle = NULL; +SDL_ELF_NOTE_DLOPEN( + "io-io_uring", + "Support for async IO through liburing", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_DRIVER_LIBURING_DYNAMIC +); + #define SDL_LIBURING_FUNCS \ SDL_LIBURING_FUNC(int, io_uring_queue_init, (unsigned entries, struct io_uring *ring, unsigned flags)) \ SDL_LIBURING_FUNC(struct io_uring_probe *,io_uring_get_probe,(void)) \ diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index dca3cfcb91..ce9c870e46 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -29,6 +29,7 @@ #include "SDL_gamepad_db.h" #include "controller_type.h" #include "usb_ids.h" +#include "hidapi/SDL_hidapi_flydigi.h" #include "hidapi/SDL_hidapi_nintendo.h" #include "../events/SDL_events_c.h" @@ -715,6 +716,33 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3))) { // GameCube driver has 12 buttons and 6 axes SDL_strlcat(mapping_string, "a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,misc3:b11,misc4:b10,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER) { + SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,leftshoulder:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a5,rightx:a2,righty:a3,start:b5,x:b2,y:b3,misc1:b8,misc2:b9,misc3:b10,misc4:b11,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_PRO) { + SDL_strlcat(mapping_string, "a:b0,b:b1,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:b2,y:b3,misc1:b11,misc2:b12,paddle1:b13,paddle2:b14,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT) { + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false)) { + // Vertical mode + SDL_strlcat(mapping_string, "back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b11,paddle2:b14,paddle4:b16,", sizeof(mapping_string)); + } else { + // Mini gamepad mode + SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,paddle2:b14,paddle4:b16,", sizeof(mapping_string)); + } + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT) { + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false)) { + // Vertical mode + SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,misc2:b12,paddle1:b13,paddle3:b15,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + } else { + // Mini gamepad mode + SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,misc2:b12,paddle1:b13,paddle3:b15,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + } + } else if (vendor == USB_VENDOR_NINTENDO && + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR) { + SDL_strlcat(mapping_string, "a:b0,b:b1,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:b2,y:b3,misc1:b11,misc2:b12,paddle1:b13,paddle2:b14,paddle3:b15,paddle4:b16,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); } else if (vendor == USB_VENDOR_NINTENDO && (guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft || guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCRight || @@ -833,7 +861,7 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) // GC Ultimate Primary Map SDL_strlcat(mapping_string, "a:b0,b:b1,x:b2,y:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b4,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b13,misc2:b14,rightshoulder:b7,rightstick:b5,righttrigger:a5,rightx:a2,righty:a3,start:b10,misc3:b8,misc4:b9,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); break; - } + } break; case USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC: switch (sub_type) { @@ -843,7 +871,7 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) break; } break; - + case USB_PRODUCT_BONZIRICHANNEL_FIREBIRD: default: // Unmapped device @@ -886,19 +914,9 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17", sizeof(mapping_string)); } else if (SDL_IsJoystickFlydigiController(vendor, product)) { SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,paddle3:b13,paddle4:b14,", sizeof(mapping_string)); - switch (guid.data[15]) { - case 20: - case 21: - case 22: - case 23: - case 28: - case 80: - case 81: - case 85: - case 105: + if (guid.data[15] >= SDL_FLYDIGI_VADER2) { // Vader series of controllers have C/Z buttons SDL_strlcat(mapping_string, "misc2:b15,misc3:b16,", sizeof(mapping_string)); - break; } } 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)); @@ -2863,8 +2881,8 @@ bool SDL_ShouldIgnoreGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version } #endif - if (name && SDL_strcmp(name, "uinput-fpc") == 0) { - // The Google Pixel fingerprint sensor reports itself as a joystick + if (name && SDL_startswith(name, "uinput-")) { + // The Google Pixel fingerprint sensor, as well as other fingerprint sensors, reports itself as a joystick return true; } diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index a8bc76f1bf..a6d047bd14 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -484,6 +484,7 @@ static Uint32 initial_gamecube_devices[] = { MAKE_VIDPID(0x0079, 0x1844), // DragonRise GameCube Controller Adapter MAKE_VIDPID(0x0079, 0x1846), // DragonRise GameCube Controller Adapter MAKE_VIDPID(0x057e, 0x0337), // Nintendo Wii U GameCube Controller Adapter + MAKE_VIDPID(0x057e, 0x2073), // Nintendo Switch 2 NSO GameCube Controller MAKE_VIDPID(0x0926, 0x8888), // Cyber Gadget GameCube Controller MAKE_VIDPID(0x0e6f, 0x0185), // PDP Wired Fight Pad Pro for Nintendo Switch MAKE_VIDPID(0x1a34, 0xf705), // GameCube {HuiJia USB box} @@ -2952,10 +2953,14 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons } else if (vendor == 0x0001 && product == 0x0001) { type = SDL_GAMEPAD_TYPE_STANDARD; - } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) { + } else if (vendor == USB_VENDOR_NINTENDO && + (product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT || + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT)) { type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT; - } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) { + } else if (vendor == USB_VENDOR_NINTENDO && + (product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT || + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT)) { if (name && SDL_strstr(name, "NES Controller") != NULL) { // We don't have a type for the Nintendo Online NES Controller type = SDL_GAMEPAD_TYPE_STANDARD; @@ -2970,7 +2975,9 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT; } - } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) { + } else if (vendor == USB_VENDOR_NINTENDO && + (product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR || + product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR)) { type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR; } else if (forUI && SDL_IsJoystickGameCube(vendor, product)) { @@ -3160,7 +3167,9 @@ bool SDL_IsJoystickNintendoSwitchJoyConGrip(Uint16 vendor_id, Uint16 product_id) bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product_id) { - return vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; + return vendor_id == USB_VENDOR_NINTENDO && + (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR || + product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR); } bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id) diff --git a/src/joystick/android/SDL_sysjoystick.c b/src/joystick/android/SDL_sysjoystick.c index 9a3402eb6a..e3693686eb 100644 --- a/src/joystick/android/SDL_sysjoystick.c +++ b/src/joystick/android/SDL_sysjoystick.c @@ -328,6 +328,10 @@ void Android_AddJoystick(int device_id, const char *name, const char *desc, int goto done; } + if (SDL_ShouldIgnoreJoystick(vendor_id, product_id, 0, name)) { + goto done; + } + #ifdef DEBUG_JOYSTICK SDL_Log("Joystick: %s, descriptor %s, vendor = 0x%.4x, product = 0x%.4x, %d axes, %d hats", name, desc, vendor_id, product_id, naxes, nhats); #endif diff --git a/src/joystick/controller_list.h b/src/joystick/controller_list.h index cd7c4871e5..f1994e3756 100644 --- a/src/joystick/controller_list.h +++ b/src/joystick/controller_list.h @@ -540,8 +540,11 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x05ac, 0x0002 ), k_eControllerType_AppleController, NULL }, // MFI Standard Gamepad (generic entry for iOS/tvOS) { MAKE_CONTROLLER_ID( 0x057e, 0x2006 ), k_eControllerType_SwitchJoyConLeft, NULL }, // Nintendo Switch Joy-Con (Left) + { MAKE_CONTROLLER_ID( 0x057e, 0x2067 ), k_eControllerType_SwitchJoyConLeft, NULL }, // Nintendo Switch 2 Joy-Con (Left) { MAKE_CONTROLLER_ID( 0x057e, 0x2007 ), k_eControllerType_SwitchJoyConRight, NULL }, // Nintendo Switch Joy-Con (Right) + { MAKE_CONTROLLER_ID( 0x057e, 0x2066 ), k_eControllerType_SwitchJoyConRight, NULL }, // Nintendo Switch 2 Joy-Con (Right) { MAKE_CONTROLLER_ID( 0x057e, 0x2008 ), k_eControllerType_SwitchJoyConPair, NULL }, // Nintendo Switch Joy-Con (Left+Right Combined) + { MAKE_CONTROLLER_ID( 0x057e, 0x2068 ), k_eControllerType_SwitchJoyConPair, NULL }, // Nintendo Switch 2 Joy-Con (Left+Right Combined) // This same controller ID is spoofed by many 3rd-party Switch controllers. // The ones we currently know of are: @@ -550,6 +553,7 @@ static const ControllerDescription_t arrControllers[] = { // * ZhiXu Gamepad Wireless // * Sunwaytek Wireless Motion Controller for Nintendo Switch { MAKE_CONTROLLER_ID( 0x057e, 0x2009 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Switch Pro Controller + { MAKE_CONTROLLER_ID( 0x057e, 0x2069 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Switch 2 Pro Controller //{ MAKE_CONTROLLER_ID( 0x057e, 0x2017 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online SNES Controller //{ MAKE_CONTROLLER_ID( 0x057e, 0x2019 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online N64 Controller //{ MAKE_CONTROLLER_ID( 0x057e, 0x201e ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online SEGA Genesis Controller diff --git a/src/joystick/emscripten/SDL_sysjoystick.c b/src/joystick/emscripten/SDL_sysjoystick.c index b481d5da73..6c51f8eaf6 100644 --- a/src/joystick/emscripten/SDL_sysjoystick.c +++ b/src/joystick/emscripten/SDL_sysjoystick.c @@ -66,8 +66,17 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep goto done; } + int first_hat_button = -1; + int num_buttons = gamepadEvent->numButtons; + if ((SDL_strcmp(gamepadEvent->mapping, "standard") == 0) && (num_buttons >= 16)) { // maps to a game console gamepad layout, turn the d-pad into a hat. + num_buttons -= 4; + first_hat_button = 12; + } + + item->first_hat_button = first_hat_button; + item->nhats = (first_hat_button >= 0) ? 1 : 0; item->naxes = gamepadEvent->numAxes; - item->nbuttons = gamepadEvent->numButtons; + item->nbuttons = num_buttons; item->device_instance = SDL_GetNextObjectID(); item->timestamp = gamepadEvent->timestamp; @@ -76,9 +85,30 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep item->axis[i] = gamepadEvent->axis[i]; } - for (i = 0; i < item->nbuttons; i++) { - item->analogButton[i] = gamepadEvent->analogButton[i]; - item->digitalButton[i] = gamepadEvent->digitalButton[i]; + int buttonidx = 0; + for (i = 0; i < item->nbuttons; i++, buttonidx++) { + if (buttonidx == first_hat_button) { + buttonidx += 3; // skip these buttons, we're treating them as hat input. + } + item->analogButton[i] = gamepadEvent->analogButton[buttonidx]; + item->digitalButton[i] = gamepadEvent->digitalButton[buttonidx]; + } + + SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons. + if (item->nhats) { + Uint8 value = SDL_HAT_CENTERED; + // this currently expects the first button to be up, then down, then left, then right. + if (gamepadEvent->digitalButton[first_hat_button + 0]) { + value |= SDL_HAT_UP; + } else if (gamepadEvent->digitalButton[first_hat_button + 1]) { + value |= SDL_HAT_DOWN; + } + if (gamepadEvent->digitalButton[first_hat_button + 2]) { + value |= SDL_HAT_LEFT; + } else if (gamepadEvent->digitalButton[first_hat_button + 3]) { + value |= SDL_HAT_RIGHT; + } + item->hat = value; } if (!SDL_joylist_tail) { @@ -305,6 +335,7 @@ static SDL_JoystickID EMSCRIPTEN_JoystickGetDeviceInstanceID(int device_index) static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index) { SDL_joylist_item *item = JoystickByDeviceIndex(device_index); + bool rumble_available = false; if (!item) { return SDL_SetError("No such device"); @@ -317,12 +348,27 @@ static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index) joystick->hwdata = (struct joystick_hwdata *)item; item->joystick = joystick; - // HTML5 Gamepad API doesn't say anything about these - joystick->nhats = 0; - + // HTML5 Gamepad API doesn't offer hats, but we can fake it from the d-pad buttons on the "standard" mapping. + joystick->nhats = item->nhats; joystick->nbuttons = item->nbuttons; joystick->naxes = item->naxes; + rumble_available = EM_ASM_INT({ + let gamepads = navigator['getGamepads'](); + if (!gamepads) { + return 0; + } + let gamepad = gamepads[$0]; + if (!gamepad || !gamepad['vibrationActuator']) { + return 0; + } + return 1; + }, item->index); + + if (rumble_available) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } + return true; } @@ -344,15 +390,21 @@ static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick) result = emscripten_get_gamepad_status(item->index, &gamepadState); if (result == EMSCRIPTEN_RESULT_SUCCESS) { if (gamepadState.timestamp == 0 || gamepadState.timestamp != item->timestamp) { - for (i = 0; i < item->nbuttons; i++) { - if (item->digitalButton[i] != gamepadState.digitalButton[i]) { - bool down = (gamepadState.digitalButton[i] != 0); + const int first_hat_button = item->first_hat_button; + + int buttonidx = 0; + for (i = 0; i < item->nbuttons; i++, buttonidx++) { + if (buttonidx == first_hat_button) { + buttonidx += 4; // skip these buttons, we're treating them as hat input. + } + if (item->digitalButton[i] != gamepadState.digitalButton[buttonidx]) { + bool down = (gamepadState.digitalButton[buttonidx] != 0); SDL_SendJoystickButton(timestamp, item->joystick, i, down); } // store values to compare them in the next update - item->analogButton[i] = gamepadState.analogButton[i]; - item->digitalButton[i] = gamepadState.digitalButton[i]; + item->analogButton[i] = gamepadState.analogButton[buttonidx]; + item->digitalButton[i] = gamepadState.digitalButton[buttonidx]; } for (i = 0; i < item->naxes; i++) { @@ -366,6 +418,27 @@ static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick) item->axis[i] = gamepadState.axis[i]; } + SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons. + if (item->nhats) { + Uint8 value = SDL_HAT_CENTERED; + // this currently expects the first button to be up, then down, then left, then right. + if (gamepadState.digitalButton[first_hat_button + 0]) { + value |= SDL_HAT_UP; + } else if (gamepadState.digitalButton[first_hat_button + 1]) { + value |= SDL_HAT_DOWN; + } + if (gamepadState.digitalButton[first_hat_button + 2]) { + value |= SDL_HAT_LEFT; + } else if (gamepadState.digitalButton[first_hat_button + 3]) { + value |= SDL_HAT_RIGHT; + } + if (item->hat != value) { + item->hat = value; + SDL_SendJoystickHat(timestamp, item->joystick, 0, value); + } + } + + item->timestamp = gamepadState.timestamp; } } @@ -390,7 +463,29 @@ static SDL_GUID EMSCRIPTEN_JoystickGetDeviceGUID(int device_index) static bool EMSCRIPTEN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { - return SDL_Unsupported(); + SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; + + // clang-format off + bool result = EM_ASM_INT({ + let gamepads = navigator['getGamepads'](); + if (!gamepads) { + return 0; + } + let gamepad = gamepads[$0]; + if (!gamepad || !gamepad['vibrationActuator']) { + return 0; + } + + gamepad['vibrationActuator']['playEffect']('dual-rumble', { + 'startDelay': 0, + 'duration': 3000, + 'weakMagnitude': $1 / 0xFFFF, + 'strongMagnitude': $2 / 0xFFFF, + }); + return 1; + }, item->index, low_frequency_rumble, high_frequency_rumble); + + return result; } static bool EMSCRIPTEN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) diff --git a/src/joystick/emscripten/SDL_sysjoystick_c.h b/src/joystick/emscripten/SDL_sysjoystick_c.h index e03a27c41f..991959a759 100644 --- a/src/joystick/emscripten/SDL_sysjoystick_c.h +++ b/src/joystick/emscripten/SDL_sysjoystick_c.h @@ -34,12 +34,15 @@ typedef struct SDL_joylist_item char *mapping; SDL_JoystickID device_instance; SDL_Joystick *joystick; + int first_hat_button; + int nhats; int nbuttons; int naxes; double timestamp; double axis[64]; double analogButton[64]; EM_BOOL digitalButton[64]; + Uint8 hat; // there is (currently) only ever one of these, faked from the d-pad buttons. struct SDL_joylist_item *next; } SDL_joylist_item; diff --git a/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/src/joystick/hidapi/SDL_hidapi_8bitdo.c index 95227869da..8b16b72533 100644 --- a/src/joystick/hidapi/SDL_hidapi_8bitdo.c +++ b/src/joystick/hidapi/SDL_hidapi_8bitdo.c @@ -270,6 +270,13 @@ static Uint64 HIDAPI_Driver8BitDo_GetIMURateForProductID(SDL_HIDAPI_Device *devi return 100; } case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: + if (device->is_bluetooth) { + // Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool + return 120; // Observed Bluetooth packet rate seems to be 120hz + } else { + // This firmware appears to update at 1000 Hz over USB dongle + return 1000; + } default: return 120; } diff --git a/src/joystick/hidapi/SDL_hidapi_flydigi.c b/src/joystick/hidapi/SDL_hidapi_flydigi.c index 938d4c960d..55048f6083 100644 --- a/src/joystick/hidapi/SDL_hidapi_flydigi.c +++ b/src/joystick/hidapi/SDL_hidapi_flydigi.c @@ -25,6 +25,7 @@ #include "../SDL_sysjoystick.h" #include "SDL_hidapijoystick_c.h" #include "SDL_hidapi_rumble.h" +#include "SDL_hidapi_flydigi.h" #ifdef SDL_JOYSTICK_HIDAPI_FLYDIGI @@ -42,10 +43,10 @@ enum SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS }; -/* Rate of IMU Sensor Packets over wireless Dongle observed in testcontroller tool at 1000hz */ +/* Rate of IMU Sensor Packets over wireless dongle observed in testcontroller at 1000hz */ #define SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ 1000 #define SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ) -/* Rate of IMU Sensor Packets over wired observed in testcontroller tool connection at 500hz */ +/* Rate of IMU Sensor Packets over wired connection observed in testcontroller at 500hz */ #define SENSOR_INTERVAL_VADER_PRO4_WIRED_RATE_HZ 500 #define SENSOR_INTERVAL_VADER_PRO4_WIRED_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER_PRO4_WIRED_RATE_HZ) @@ -147,70 +148,104 @@ static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) } } - if (ctx->deviceID == 0) { + Uint8 controller_type = SDL_FLYDIGI_UNKNOWN; + switch (ctx->deviceID) { + case 19: + controller_type = SDL_FLYDIGI_APEX2; + break; + case 24: + case 26: + case 29: + controller_type = SDL_FLYDIGI_APEX3; + break; + case 84: + controller_type = SDL_FLYDIGI_APEX4; + break; + case 20: + case 21: + case 23: + controller_type = SDL_FLYDIGI_VADER2; + break; + case 22: + controller_type = SDL_FLYDIGI_VADER2_PRO; + break; + case 28: + controller_type = SDL_FLYDIGI_VADER3; + break; + case 80: + case 81: + controller_type = SDL_FLYDIGI_VADER3_PRO; + break; + case 85: + case 91: + case 105: + controller_type = SDL_FLYDIGI_VADER4_PRO; + break; + default: // Try to guess from the name of the controller if (SDL_strstr(device->name, "VADER") != NULL) { if (SDL_strstr(device->name, "VADER2") != NULL) { - ctx->deviceID = 20; + controller_type = SDL_FLYDIGI_VADER2; } else if (SDL_strstr(device->name, "VADER3") != NULL) { - ctx->deviceID = 28; + controller_type = SDL_FLYDIGI_VADER3; } else if (SDL_strstr(device->name, "VADER4") != NULL) { - ctx->deviceID = 85; + controller_type = SDL_FLYDIGI_VADER4; } } else if (SDL_strstr(device->name, "APEX") != NULL) { if (SDL_strstr(device->name, "APEX2") != NULL) { - ctx->deviceID = 19; + controller_type = SDL_FLYDIGI_APEX2; } else if (SDL_strstr(device->name, "APEX3") != NULL) { - ctx->deviceID = 24; + controller_type = SDL_FLYDIGI_APEX3; } else if (SDL_strstr(device->name, "APEX4") != NULL) { - ctx->deviceID = 84; + controller_type = SDL_FLYDIGI_APEX4; + } else if (SDL_strstr(device->name, "APEX5") != NULL) { + controller_type = SDL_FLYDIGI_APEX5; } } + break; } - device->guid.data[15] = ctx->deviceID; + device->guid.data[15] = controller_type; // This is the previous sensor default of 125hz. // Override this in the switch statement below based on observed sensor packet rate. ctx->sensor_timestamp_step_ns = SDL_NS_PER_SECOND / 125; - switch (ctx->deviceID) { - case 19: + switch (controller_type) { + case SDL_FLYDIGI_APEX2: HIDAPI_SetDeviceName(device, "Flydigi Apex 2"); break; - case 24: - case 26: - case 29: + case SDL_FLYDIGI_APEX3: HIDAPI_SetDeviceName(device, "Flydigi Apex 3"); break; - case 84: + case SDL_FLYDIGI_APEX4: // The Apex 4 controller has sensors, but they're only reported when gyro mouse is enabled HIDAPI_SetDeviceName(device, "Flydigi Apex 4"); break; - case 20: - case 21: - case 23: + case SDL_FLYDIGI_APEX5: + HIDAPI_SetDeviceName(device, "Flydigi Apex 5"); + break; + case SDL_FLYDIGI_VADER2: // The Vader 2 controller has sensors, but they're only reported when gyro mouse is enabled HIDAPI_SetDeviceName(device, "Flydigi Vader 2"); ctx->has_cz = true; break; - case 22: + case SDL_FLYDIGI_VADER2_PRO: HIDAPI_SetDeviceName(device, "Flydigi Vader 2 Pro"); ctx->has_cz = true; break; - case 28: + case SDL_FLYDIGI_VADER3: HIDAPI_SetDeviceName(device, "Flydigi Vader 3"); ctx->has_cz = true; break; - case 80: - case 81: + case SDL_FLYDIGI_VADER3_PRO: HIDAPI_SetDeviceName(device, "Flydigi Vader 3 Pro"); ctx->has_cz = true; ctx->sensors_supported = true; ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f; ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER_PRO4_WIRED_NS; break; - case 85: - case 105: + case SDL_FLYDIGI_VADER4: + case SDL_FLYDIGI_VADER4_PRO: HIDAPI_SetDeviceName(device, "Flydigi Vader 4 Pro"); ctx->has_cz = true; ctx->sensors_supported = true; @@ -270,7 +305,6 @@ static bool HIDAPI_DriverFlydigi_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy } if (ctx->sensors_supported) { - const float flSensorRate = ctx->wireless ? (float)SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ : (float)SENSOR_INTERVAL_VADER_PRO4_WIRED_RATE_HZ; SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, flSensorRate); SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, flSensorRate); @@ -324,7 +358,7 @@ static void HIDAPI_DriverFlydigi_HandleStatePacket(SDL_Joystick *joystick, SDL_D { Sint16 axis; Uint64 timestamp = SDL_GetTicksNS(); - if (data[0] != 0x04 && data[0] != 0xFE) { + if (data[0] != 0x04 || data[1] != 0xFE) { // We don't know how to handle this report return; } @@ -437,10 +471,10 @@ static void HIDAPI_DriverFlydigi_HandleStatePacket(SDL_Joystick *joystick, SDL_D // These values were estimated using the testcontroller tool in lieux of hard data sheet references. const float flPitchAndYawScale = DEG2RAD(72000.0f); const float flRollScale = DEG2RAD(1200.0f); + values[0] = HIDAPI_RemapVal(-1.0f * LOAD16(data[26], data[27]), INT16_MIN, INT16_MAX, -flPitchAndYawScale, flPitchAndYawScale); values[1] = HIDAPI_RemapVal(-1.0f * LOAD16(data[18], data[20]), INT16_MIN, INT16_MAX, -flPitchAndYawScale, flPitchAndYawScale); values[2] = HIDAPI_RemapVal(-1.0f * LOAD16(data[29], data[30]), INT16_MIN, INT16_MAX, -flRollScale, flRollScale); - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3); values[0] = -LOAD16(data[11], data[12]) * ctx->accelScale; // Acceleration along pitch axis diff --git a/src/video/psp/SDL_pspmouse.c b/src/joystick/hidapi/SDL_hidapi_flydigi.h similarity index 65% rename from src/video/psp/SDL_pspmouse.c rename to src/joystick/hidapi/SDL_hidapi_flydigi.h index e63be96c2a..42d6ef7ee2 100644 --- a/src/video/psp/SDL_pspmouse.c +++ b/src/joystick/hidapi/SDL_hidapi_flydigi.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2024 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 @@ -18,20 +18,21 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ -#include "SDL_internal.h" -#ifdef SDL_VIDEO_DRIVER_PSP +// These are values used in the controller type byte of the controller GUID -#include - -#include "../../events/SDL_events_c.h" - -#include "SDL_pspmouse_c.h" - -// The implementation dependent data for the window manager cursor -struct WMcursor +typedef enum { - int unused; -}; + SDL_FLYDIGI_UNKNOWN, + SDL_FLYDIGI_APEX2 = (1 << 0), + SDL_FLYDIGI_APEX3, + SDL_FLYDIGI_APEX4, + SDL_FLYDIGI_APEX5, + SDL_FLYDIGI_VADER2 = (1 << 4), + SDL_FLYDIGI_VADER2_PRO, + SDL_FLYDIGI_VADER3, + SDL_FLYDIGI_VADER3_PRO, + SDL_FLYDIGI_VADER4, + SDL_FLYDIGI_VADER4_PRO, +} SDL_FlyDigiControllerType; -#endif // SDL_VIDEO_DRIVER_PSP diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c index 7038e4f405..92d14ab2f6 100644 --- a/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/src/joystick/hidapi/SDL_hidapi_switch.c @@ -1388,7 +1388,15 @@ static bool HIDAPI_DriverSwitch_IsSupportedDevice(SDL_HIDAPI_Device *device, con return false; } - return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO); + if (type != SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO) { + return false; + } + + // The Nintendo Switch 2 Pro uses another driver + if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH2_PRO) { + return false; + } + return true; } static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) diff --git a/src/joystick/hidapi/SDL_hidapi_switch2.c b/src/joystick/hidapi/SDL_hidapi_switch2.c new file mode 100644 index 0000000000..d7c4e8d98a --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapi_switch2.c @@ -0,0 +1,993 @@ +/* + 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. +*/ +/* This driver supports the Nintendo Switch Pro controller. + Code and logic contributed by Valve Corporation under the SDL zlib license. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../../SDL_hints_c.h" +#include "../../misc/SDL_libusb.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" + +#ifdef SDL_JOYSTICK_HIDAPI_SWITCH2 + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_SWITCH2_PROTOCOL +#endif + +enum +{ + SDL_GAMEPAD_BUTTON_SWITCH2_PRO_SHARE = 11, + SDL_GAMEPAD_BUTTON_SWITCH2_PRO_C, + SDL_GAMEPAD_BUTTON_SWITCH2_PRO_RIGHT_PADDLE, + SDL_GAMEPAD_BUTTON_SWITCH2_PRO_LEFT_PADDLE, + SDL_GAMEPAD_NUM_SWITCH2_PRO_BUTTONS +}; + +enum +{ + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_SHARE = 11, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_C, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE1, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE1, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE2, + SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE2, + SDL_GAMEPAD_NUM_SWITCH2_JOYCON_BUTTONS +}; + +enum +{ + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_GUIDE = 4, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_START, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_LEFT_SHOULDER, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_RIGHT_SHOULDER, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_SHARE, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_C, + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_LEFT_TRIGGER, // Full trigger pull click + SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_RIGHT_TRIGGER, // Full trigger pull click + SDL_GAMEPAD_NUM_SWITCH2_GAMECUBE_BUTTONS +}; + +typedef struct +{ + Uint16 neutral; + Uint16 max; + Uint16 min; +} Switch2_AxisCalibration; + +typedef struct +{ + Switch2_AxisCalibration x; + Switch2_AxisCalibration y; +} Switch2_StickCalibration; + +typedef struct +{ + SDL_HIDAPI_Device *device; + SDL_Joystick *joystick; + + SDL_LibUSBContext *libusb; + libusb_device_handle *device_handle; + bool interface_claimed; + Uint8 interface_number; + Uint8 out_endpoint; + Uint8 in_endpoint; + + Switch2_StickCalibration left_stick; + Switch2_StickCalibration right_stick; + Uint8 left_trigger_max; + Uint8 right_trigger_max; + + bool player_lights; + int player_index; + + bool vertical_mode; + Uint8 last_state[USB_PACKET_LENGTH]; +} SDL_DriverSwitch2_Context; + +static void ParseStickCalibration(Switch2_StickCalibration *stick_data, const Uint8 *data) +{ + stick_data->x.neutral = data[0]; + stick_data->x.neutral |= (data[1] & 0x0F) << 8; + + stick_data->y.neutral = data[1] >> 4; + stick_data->y.neutral |= data[2] << 4; + + stick_data->x.max = data[3]; + stick_data->x.max |= (data[4] & 0x0F) << 8; + + stick_data->y.max = data[4] >> 4; + stick_data->y.max |= data[5] << 4; + + stick_data->x.min = data[6]; + stick_data->x.min |= (data[7] & 0x0F) << 8; + + stick_data->y.min = data[7] >> 4; + stick_data->y.min |= data[8] << 4; +} + +static int SendBulkData(SDL_DriverSwitch2_Context *ctx, const Uint8 *data, unsigned size) +{ + int transferred; + int res = ctx->libusb->bulk_transfer(ctx->device_handle, + ctx->out_endpoint, + (Uint8 *)data, + size, + &transferred, + 1000); + if (res < 0) { + return res; + } + return transferred; +} + +static int RecvBulkData(SDL_DriverSwitch2_Context *ctx, Uint8 *data, unsigned size) +{ + int transferred; + int total_transferred = 0; + int res; + + while (size > 0) { + unsigned current_read = size; + if (current_read > 64) { + current_read = 64; + } + res = ctx->libusb->bulk_transfer(ctx->device_handle, + ctx->in_endpoint, + data, + current_read, + &transferred, + 100); + if (res < 0) { + return res; + } + total_transferred += transferred; + size -= transferred; + data += current_read; + if ((unsigned) transferred < current_read) { + break; + } + } + + return total_transferred; +} + +static void MapJoystickAxis(Uint64 timestamp, SDL_Joystick *joystick, Uint8 axis, const Switch2_AxisCalibration *calib, float value, bool invert) +{ + Sint16 mapped_value; + if (calib && calib->neutral && calib->min && calib->max) { + value -= calib->neutral; + if (value < 0) { + value /= calib->min; + } else { + value /= calib->max; + } + mapped_value = (Sint16) SDL_clamp(value * SDL_MAX_SINT16, SDL_MIN_SINT16, SDL_MAX_SINT16); + } else { + mapped_value = (Sint16) HIDAPI_RemapVal(value, 0, 4096, SDL_MIN_SINT16, SDL_MAX_SINT16); + } + if (invert) { + mapped_value = ~mapped_value; + } + SDL_SendJoystickAxis(timestamp, joystick, axis, mapped_value); +} + +static void MapTriggerAxis(Uint64 timestamp, SDL_Joystick *joystick, Uint8 axis, Uint8 max, float value) +{ + Sint16 mapped_value = (Sint16) HIDAPI_RemapVal( + SDL_clamp((value - max) / (232.f - max), 0, 1), + 0, 1, + SDL_MIN_SINT16, SDL_MAX_SINT16 + ); + SDL_SendJoystickAxis(timestamp, joystick, axis, mapped_value); +} + +static bool UpdateSlotLED(SDL_DriverSwitch2_Context *ctx) +{ + unsigned char SET_LED_DATA[] = { + 0x09, 0x91, 0x00, 0x07, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + unsigned char calibration_data[0x50] = {0}; + + if (ctx->player_lights && ctx->player_index >= 0) { + SET_LED_DATA[8] = (1 << (ctx->player_index % 4)); + } + int res = SendBulkData(ctx, SET_LED_DATA, sizeof(SET_LED_DATA)); + if (res < 0) { + return SDL_SetError("Couldn't set LED data: %d\n", res); + } + return (RecvBulkData(ctx, calibration_data, 0x40) > 0); +} + +static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)userdata; + bool player_lights = SDL_GetStringBoolean(hint, true); + + if (player_lights != ctx->player_lights) { + ctx->player_lights = player_lights; + + UpdateSlotLED(ctx); + HIDAPI_UpdateDeviceProperties(ctx->device); + } +} + +static void HIDAPI_DriverSwitch2_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, callback, userdata); +} + +static void HIDAPI_DriverSwitch2_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, callback, userdata); +} + +static bool HIDAPI_DriverSwitch2_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverSwitch2_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) +{ + if (vendor_id == USB_VENDOR_NINTENDO) { + switch (product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + return true; + } + } + + return false; +} + +static bool HIDAPI_DriverSwitch2_InitBluetooth(SDL_HIDAPI_Device *device) +{ + // FIXME: Need to add Bluetooth support + return SDL_SetError("Nintendo Switch2 controllers not supported over Bluetooth"); +} + +static bool FindBulkEndpoints(SDL_LibUSBContext *libusb, libusb_device_handle *handle, Uint8 *bInterfaceNumber, Uint8 *out_endpoint, Uint8 *in_endpoint) +{ + struct libusb_config_descriptor *config; + int found = 0; + + if (libusb->get_config_descriptor(libusb->get_device(handle), 0, &config) != 0) { + return false; + } + + for (int i = 0; i < config->bNumInterfaces; i++) { + const struct libusb_interface *iface = &config->interface[i]; + for (int j = 0; j < iface->num_altsetting; j++) { + const struct libusb_interface_descriptor *altsetting = &iface->altsetting[j]; + if (altsetting->bInterfaceNumber == 1) { + for (int k = 0; k < altsetting->bNumEndpoints; k++) { + const struct libusb_endpoint_descriptor *ep = &altsetting->endpoint[k]; + if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK) { + *bInterfaceNumber = altsetting->bInterfaceNumber; + if ((ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) { + *out_endpoint = ep->bEndpointAddress; + found |= 1; + } + if ((ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) { + *in_endpoint = ep->bEndpointAddress; + found |= 2; + } + if (found == 3) { + libusb->free_config_descriptor(config); + return true; + } + } + } + } + } + } + libusb->free_config_descriptor(config); + return false; +} + +static bool HIDAPI_DriverSwitch2_InitUSB(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + if (!SDL_InitLibUSB(&ctx->libusb)) { + return false; + } + + ctx->device_handle = (libusb_device_handle *)SDL_GetPointerProperty(SDL_hid_get_properties(device->dev), SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER, NULL); + if (!ctx->device_handle) { + return SDL_SetError("Couldn't get libusb device handle"); + } + + if (!FindBulkEndpoints(ctx->libusb, ctx->device_handle, &ctx->interface_number, &ctx->out_endpoint, &ctx->in_endpoint)) { + return SDL_SetError("Couldn't find bulk endpoints"); + } + + int res = ctx->libusb->claim_interface(ctx->device_handle, ctx->interface_number); + if (res < 0) { + return SDL_SetError("Couldn't claim interface %d: %d\n", ctx->interface_number, res); + } + ctx->interface_claimed = true; + + const unsigned char INIT_DATA[] = { + 0x03, 0x91, 0x00, 0x0d, 0x00, 0x08, 0x00, 0x00, + 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + unsigned char flash_read_command[] = { + 0x02, 0x91, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00 + }; + unsigned char calibration_data[0x50] = {0}; + + res = SendBulkData(ctx, INIT_DATA, sizeof(INIT_DATA)); + if (res < 0) { + return SDL_SetError("Couldn't send initialization data: %d\n", res); + } + RecvBulkData(ctx, calibration_data, 0x40); + + // Wait for initialization to complete + SDL_Delay(1); + + flash_read_command[12] = 0x80; + res = SendBulkData(ctx, flash_read_command, sizeof(flash_read_command)); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't request calibration data: %d", res); + } else { + res = RecvBulkData(ctx, calibration_data, sizeof(calibration_data)); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read calibration data: %d", res); + } else { + ParseStickCalibration(&ctx->left_stick, &calibration_data[0x38]); + } + } + + + flash_read_command[12] = 0xC0; + res = SendBulkData(ctx, flash_read_command, sizeof(flash_read_command)); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't request calibration data: %d", res); + } else { + res = RecvBulkData(ctx, calibration_data, sizeof(calibration_data)); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read calibration data: %d", res); + } else { + ParseStickCalibration(&ctx->right_stick, &calibration_data[0x38]); + } + } + + if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER) { + flash_read_command[12] = 0x40; + flash_read_command[13] = 0x31; + res = SendBulkData(ctx, flash_read_command, sizeof(flash_read_command)); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read calibration data: %d", res); + } else { + res = RecvBulkData(ctx, calibration_data, sizeof(calibration_data)); + if (res < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't read calibration data: %d", res); + } else { + ctx->left_trigger_max = calibration_data[0x10]; + ctx->right_trigger_max = calibration_data[0x11]; + } + } + } + + return true; +} + +static bool HIDAPI_DriverSwitch2_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch2_Context *ctx; + + ctx = (SDL_DriverSwitch2_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + device->context = ctx; + + if (device->is_bluetooth) { + if (!HIDAPI_DriverSwitch2_InitBluetooth(device)) { + return false; + } + } else { + if (!HIDAPI_DriverSwitch2_InitUSB(device)) { + return false; + } + } + + // Sometimes the device handle isn't available during enumeration so we don't get the device name, so set it explicitly + switch (device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: + HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller"); + break; + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller"); + break; + default: + break; + } + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverSwitch2_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverSwitch2_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + if (!ctx->joystick) { + return; + } + + ctx->player_index = player_index; + + UpdateSlotLED(ctx); +} + +static bool HIDAPI_DriverSwitch2_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + ctx->joystick = joystick; + + // Initialize player index (needed for setting LEDs) + ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); + ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, true); + UpdateSlotLED(ctx); + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + // Initialize the joystick capabilities + switch (device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH2_GAMECUBE_BUTTONS; + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH2_JOYCON_BUTTONS; + break; + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH2_PRO_BUTTONS; + break; + default: + // FIXME: How many buttons does this have? + break; + } + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + // Set up for vertical mode + ctx->vertical_mode = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false); + + return true; +} + +static bool HIDAPI_DriverSwitch2_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSwitch2_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverSwitch2_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + Uint32 result = 0; + + if (ctx->player_lights) { + result |= SDL_JOYSTICK_CAP_PLAYER_LED; + } + return result; +} + +static bool HIDAPI_DriverSwitch2_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSwitch2_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverSwitch2_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void HandleGameCubeState(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + if (data[3] != ctx->last_state[3]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_RIGHT_TRIGGER, ((data[3] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_RIGHT_SHOULDER, ((data[3] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_START, ((data[3] & 0x40) != 0)); + } + + if (data[4] != ctx->last_state[4]) { + Uint8 hat = 0; + + if (data[4] & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data[4] & 0x02) { + hat |= SDL_HAT_RIGHT; + } + if (data[4] & 0x04) { + hat |= SDL_HAT_LEFT; + } + if (data[4] & 0x08) { + hat |= SDL_HAT_UP; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_LEFT_TRIGGER, ((data[4] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_LEFT_SHOULDER, ((data[4] & 0x20) != 0)); + } + + if (data[5] != ctx->last_state[5]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_GUIDE, ((data[5] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_SHARE, ((data[5] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_GAMECUBE_C, ((data[5] & 0x10) != 0)); + } + + MapTriggerAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFT_TRIGGER, + ctx->left_trigger_max, + data[13] + ); + MapTriggerAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, + ctx->right_trigger_max, + data[14] + ); + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.x, + (float) (data[6] | ((data[7] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.y, + (float) ((data[7] >> 4) | (data[8] << 4)), + true + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTX, + &ctx->right_stick.x, + (float) (data[9] | ((data[10] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTY, + &ctx->right_stick.y, + (float)((data[10] >> 4) | (data[11] << 4)), + true + ); +} + +static void HandleCombinedControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + // FIXME: When we find out what the SL and SR buttons are, map them to paddles + + if (data[3] != ctx->last_state[3]) { + Uint8 hat = 0; + + if (data[3] & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data[3] & 0x02) { + hat |= SDL_HAT_RIGHT; + } + if (data[3] & 0x04) { + hat |= SDL_HAT_LEFT; + } + if (data[3] & 0x08) { + hat |= SDL_HAT_UP; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[3] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x80) != 0)); + } + + if (data[4] != ctx->last_state[4]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_SHARE, ((data[4] & 0x01) != 0)); + } + + Sint16 axis = (data[3] & 0x20) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.x, + (float) (data[6] | ((data[7] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.y, + (float) ((data[7] >> 4) | (data[8] << 4)), + true + ); +} + +static void HandleMiniControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + // FIXME: When we find out what the SL and SR buttons are, map them to shoulder buttons + + if (data[3] != ctx->last_state[3]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[3] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE1, ((data[3] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_LEFT_PADDLE2, ((data[3] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x80) != 0)); + } + + if (data[4] != ctx->last_state[4]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[4] & 0x01) != 0)); + } + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.y, + (float) ((data[7] >> 4) | (data[8] << 4)), + true + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.x, + (float) (data[6] | ((data[7] & 0x0F) << 8)), + true + ); +} + +static void HandleCombinedControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + // FIXME: When we find out what the SL and SR buttons are, map them to paddles + + if (data[3] != ctx->last_state[3]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[3] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[3] & 0x80) != 0)); + } + + if (data[4] != ctx->last_state[4]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[4] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_C, ((data[4] & 0x10) != 0)); + } + + Sint16 axis = (data[3] & 0x20) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTX, + &ctx->left_stick.x, + (float) (data[6] | ((data[7] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTY, + &ctx->left_stick.y, + (float) ((data[7] >> 4) | (data[8] << 4)), + true + ); +} + +static void HandleMiniControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + // FIXME: When we find out what the SL and SR buttons are, map them to shoulder buttons + + if (data[3] != ctx->last_state[3]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE1, ((data[3] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_RIGHT_PADDLE2, ((data[3] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[3] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x80) != 0)); + } + + if (data[4] != ctx->last_state[4]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[4] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_JOYCON_C, ((data[4] & 0x10) != 0)); + } + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.y, + (float) ((data[7] >> 4) | (data[8] << 4)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.x, + (float) (data[6] | ((data[7] & 0x0F) << 8)), + false + ); +} + +static void HandleSwitchProState(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + + if (data[3] != ctx->last_state[3]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[3] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[3] & 0x80) != 0)); + } + + if (data[4] != ctx->last_state[4]) { + Uint8 hat = 0; + + if (data[4] & 0x01) { + hat |= SDL_HAT_DOWN; + } + if (data[4] & 0x02) { + hat |= SDL_HAT_RIGHT; + } + if (data[4] & 0x04) { + hat |= SDL_HAT_LEFT; + } + if (data[4] & 0x08) { + hat |= SDL_HAT_UP; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[4] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[4] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[4] & 0x80) != 0)); + } + + if (data[5] != ctx->last_state[5]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[5] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_SHARE, ((data[5] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_RIGHT_PADDLE, ((data[5] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_LEFT_PADDLE, ((data[5] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_PRO_C, ((data[5] & 0x10) != 0)); + } + + axis = (data[4] & 0x20) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + axis = (data[3] & 0x20) ? 32767 : -32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTX, + &ctx->left_stick.x, + (float) (data[6] | ((data[7] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_LEFTY, + &ctx->left_stick.y, + (float) ((data[7] >> 4) | (data[8] << 4)), + true + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTX, + &ctx->right_stick.x, + (float) (data[9] | ((data[10] & 0x0F) << 8)), + false + ); + MapJoystickAxis( + timestamp, + joystick, + SDL_GAMEPAD_AXIS_RIGHTY, + &ctx->right_stick.y, + (float)((data[10] >> 4) | (data[11] << 4)), + true + ); +} + +static void HIDAPI_DriverSwitch2_HandleStatePacket(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size) +{ + Uint64 timestamp = SDL_GetTicksNS(); + + if (size < 15) { + // We don't know how to handle this report + return; + } + + switch (device->product_id) { + case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: + HandleGameCubeState(timestamp, joystick, ctx, data, size); + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT: + if (device->parent || ctx->vertical_mode) { + HandleCombinedControllerStateL(timestamp, joystick, ctx, data, size); + } else { + HandleMiniControllerStateL(timestamp, joystick, ctx, data, size); + } + break; + case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT: + if (device->parent || ctx->vertical_mode) { + HandleCombinedControllerStateR(timestamp, joystick, ctx, data, size); + } else { + HandleMiniControllerStateR(timestamp, joystick, ctx, data, size); + } + break; + case USB_PRODUCT_NINTENDO_SWITCH2_PRO: + HandleSwitchProState(timestamp, joystick, ctx, data, size); + break; + default: + // FIXME: Need state handling implementation + break; + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverSwitch2_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_SWITCH2_PROTOCOL + if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT) { + HIDAPI_DumpPacket("Nintendo Joy-Con(L) packet: size = %d", data, size); + } else if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT) { + HIDAPI_DumpPacket("Nintendo Joy-Con(R) packet: size = %d", data, size); + } else { + HIDAPI_DumpPacket("Nintendo Switch2 packet: size = %d", data, size); + } +#endif + if (!joystick) { + continue; + } + + HIDAPI_DriverSwitch2_HandleStatePacket(device, joystick, ctx, data, size); + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverSwitch2_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + ctx->joystick = NULL; +} + +static void HIDAPI_DriverSwitch2_FreeDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + if (ctx) { + if (ctx->interface_claimed) { + ctx->libusb->release_interface(ctx->device_handle, ctx->interface_number); + ctx->interface_claimed = false; + } + if (ctx->libusb) { + SDL_QuitLibUSB(); + ctx->libusb = NULL; + } + } +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch2 = { + SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, + true, + HIDAPI_DriverSwitch2_RegisterHints, + HIDAPI_DriverSwitch2_UnregisterHints, + HIDAPI_DriverSwitch2_IsEnabled, + HIDAPI_DriverSwitch2_IsSupportedDevice, + HIDAPI_DriverSwitch2_InitDevice, + HIDAPI_DriverSwitch2_GetDevicePlayerIndex, + HIDAPI_DriverSwitch2_SetDevicePlayerIndex, + HIDAPI_DriverSwitch2_UpdateDevice, + HIDAPI_DriverSwitch2_OpenJoystick, + HIDAPI_DriverSwitch2_RumbleJoystick, + HIDAPI_DriverSwitch2_RumbleJoystickTriggers, + HIDAPI_DriverSwitch2_GetJoystickCapabilities, + HIDAPI_DriverSwitch2_SetJoystickLED, + HIDAPI_DriverSwitch2_SendJoystickEffect, + HIDAPI_DriverSwitch2_SetJoystickSensorsEnabled, + HIDAPI_DriverSwitch2_CloseJoystick, + HIDAPI_DriverSwitch2_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_SWITCH2 + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index 5124d97a91..e05ee930b3 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -75,6 +75,9 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { &SDL_HIDAPI_DriverJoyCons, &SDL_HIDAPI_DriverSwitch, #endif +#ifdef SDL_JOYSTICK_HIDAPI_SWITCH2 + &SDL_HIDAPI_DriverSwitch2, +#endif #ifdef SDL_JOYSTICK_HIDAPI_WII &SDL_HIDAPI_DriverWii, #endif @@ -449,7 +452,9 @@ static void HIDAPI_SetupDeviceDriver(SDL_HIDAPI_Device *device, bool *removed) S if (device->driver) { bool enabled; - if (device->vendor_id == USB_VENDOR_NINTENDO && device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) { + if (device->vendor_id == USB_VENDOR_NINTENDO && + (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR || + device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR)) { enabled = SDL_HIDAPI_combine_joycons; } else { enabled = device->driver->enabled; @@ -1065,7 +1070,11 @@ static bool HIDAPI_CreateCombinedJoyCons(void) SDL_zero(info); info.path = "nintendo_joycons_combined"; info.vendor_id = USB_VENDOR_NINTENDO; - info.product_id = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; + if (joycons[0]->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) { + info.product_id = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR; + } else { + info.product_id = USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR; + } info.interface_number = -1; info.usage_page = USB_USAGEPAGE_GENERIC_DESKTOP; info.usage = USB_USAGE_GENERIC_GAMEPAD; diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index e280c86aa7..a8962468fb 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -35,6 +35,9 @@ #define SDL_JOYSTICK_HIDAPI_STEAM #define SDL_JOYSTICK_HIDAPI_STEAMDECK #define SDL_JOYSTICK_HIDAPI_SWITCH +#ifdef HAVE_LIBUSB +#define SDL_JOYSTICK_HIDAPI_SWITCH2 +#endif #define SDL_JOYSTICK_HIDAPI_WII #define SDL_JOYSTICK_HIDAPI_XBOX360 #define SDL_JOYSTICK_HIDAPI_XBOXONE @@ -158,6 +161,7 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch2; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W; diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index b98c3ebb74..8557e95831 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -103,6 +103,11 @@ #define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR 0x2008 // Used by joycond #define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT 0x2007 #define USB_PRODUCT_NINTENDO_SWITCH_PRO 0x2009 +#define USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER 0x2073 +#define USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT 0x2067 +#define USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR 0x2068 +#define USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT 0x2066 +#define USB_PRODUCT_NINTENDO_SWITCH2_PRO 0x2069 #define USB_PRODUCT_NINTENDO_WII_REMOTE 0x0306 #define USB_PRODUCT_NINTENDO_WII_REMOTE2 0x0330 #define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103 0x7210 @@ -165,7 +170,7 @@ #define USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC 0x10c6 #define USB_PRODUCT_HANDHELDLEGEND_PROGCC 0x10df #define USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE 0x10dd -#define USB_PRODUCT_BONZIRICHANNEL_FIREBIRD 0x10e0 +#define USB_PRODUCT_BONZIRICHANNEL_FIREBIRD 0x10e0 // USB usage pages #define USB_USAGEPAGE_GENERIC_DESKTOP 0x0001 diff --git a/src/misc/SDL_libusb.c b/src/misc/SDL_libusb.c new file mode 100644 index 0000000000..8497d878b5 --- /dev/null +++ b/src/misc/SDL_libusb.c @@ -0,0 +1,114 @@ +/* + 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_internal.h" + +#include "SDL_libusb.h" + +#ifdef HAVE_LIBUSB + +#ifdef SDL_LIBUSB_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "libusb", + "Support for joysticks through libusb", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_LIBUSB_DYNAMIC +); +#endif + +static SDL_AtomicInt SDL_libusb_refcount; +static bool SDL_libusb_loaded; +static SDL_SharedObject *SDL_libusb_handle; +static SDL_LibUSBContext SDL_libusb_context; + +bool SDL_InitLibUSB(SDL_LibUSBContext **ctx) +{ + if (SDL_AtomicIncRef(&SDL_libusb_refcount) == 0) { +#ifdef SDL_LIBUSB_DYNAMIC + SDL_libusb_handle = SDL_LoadObject(SDL_LIBUSB_DYNAMIC); + if (SDL_libusb_handle) +#endif + { + SDL_libusb_loaded = true; +#ifdef SDL_LIBUSB_DYNAMIC +#define LOAD_LIBUSB_SYMBOL(type, func) \ + if ((SDL_libusb_context.func = (type)SDL_LoadFunction(SDL_libusb_handle, "libusb_" #func)) == NULL) { \ + SDL_libusb_loaded = false; \ + } +#else +#define LOAD_LIBUSB_SYMBOL(type, func) \ + SDL_libusb_context.func = libusb_##func; +#endif + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context **), init) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_context *), exit) + LOAD_LIBUSB_SYMBOL(ssize_t (LIBUSB_CALL *)(libusb_context *, libusb_device ***), get_device_list) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_device **, int), free_device_list) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, struct libusb_device_descriptor *), get_device_descriptor) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, struct libusb_config_descriptor **), get_active_config_descriptor) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, uint8_t, struct libusb_config_descriptor **), get_config_descriptor) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_config_descriptor *), free_config_descriptor) + LOAD_LIBUSB_SYMBOL(uint8_t (LIBUSB_CALL *)(libusb_device *), get_bus_number) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len), get_port_numbers) + LOAD_LIBUSB_SYMBOL(uint8_t (LIBUSB_CALL *)(libusb_device *), get_device_address) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device *, libusb_device_handle **), open) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_device_handle *), close) + LOAD_LIBUSB_SYMBOL(libusb_device * (LIBUSB_CALL *)(libusb_device_handle *dev_handle), get_device) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), claim_interface) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), release_interface) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), kernel_driver_active) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), detach_kernel_driver) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int), attach_kernel_driver) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, int, int), set_interface_alt_setting) + LOAD_LIBUSB_SYMBOL(struct libusb_transfer * (LIBUSB_CALL *)(int), alloc_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(struct libusb_transfer *), submit_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(struct libusb_transfer *), cancel_transfer) + LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(struct libusb_transfer *), free_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, uint8_t, uint8_t, uint16_t, uint16_t, unsigned char *, uint16_t, unsigned int), control_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), interrupt_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), bulk_transfer) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *), handle_events) + LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *, int *), handle_events_completed) + LOAD_LIBUSB_SYMBOL(const char * (LIBUSB_CALL *)(int), error_name) +#undef LOAD_LIBUSB_SYMBOL + } + } + + if (SDL_libusb_loaded) { + *ctx = &SDL_libusb_context; + return true; + } else { + SDL_QuitLibUSB(); + *ctx = NULL; + return false; + } +} + +void SDL_QuitLibUSB(void) +{ + if (SDL_AtomicDecRef(&SDL_libusb_refcount)) { + if (SDL_libusb_handle) { + SDL_UnloadObject(SDL_libusb_handle); + SDL_libusb_handle = NULL; + } + SDL_libusb_loaded = false; + } +} + +#endif // HAVE_LIBUSB diff --git a/src/misc/SDL_libusb.h b/src/misc/SDL_libusb.h new file mode 100644 index 0000000000..896916f623 --- /dev/null +++ b/src/misc/SDL_libusb.h @@ -0,0 +1,97 @@ +/* + 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_internal.h" + +#ifdef HAVE_LIBUSB +// libusb HIDAPI Implementation + +// Include this now, for our dynamically-loaded libusb context +#include + +typedef struct SDL_LibUSBContext +{ +/* *INDENT-OFF* */ // clang-format off + int (LIBUSB_CALL *init)(libusb_context **ctx); + void (LIBUSB_CALL *exit)(libusb_context *ctx); + ssize_t (LIBUSB_CALL *get_device_list)(libusb_context *ctx, libusb_device ***list); + void (LIBUSB_CALL *free_device_list)(libusb_device **list, int unref_devices); + int (LIBUSB_CALL *get_device_descriptor)(libusb_device *dev, struct libusb_device_descriptor *desc); + int (LIBUSB_CALL *get_active_config_descriptor)(libusb_device *dev, struct libusb_config_descriptor **config); + int (LIBUSB_CALL *get_config_descriptor)( + libusb_device *dev, + uint8_t config_index, + struct libusb_config_descriptor **config + ); + void (LIBUSB_CALL *free_config_descriptor)(struct libusb_config_descriptor *config); + uint8_t (LIBUSB_CALL *get_bus_number)(libusb_device *dev); + int (LIBUSB_CALL *get_port_numbers)(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len); + uint8_t (LIBUSB_CALL *get_device_address)(libusb_device *dev); + int (LIBUSB_CALL *open)(libusb_device *dev, libusb_device_handle **dev_handle); + void (LIBUSB_CALL *close)(libusb_device_handle *dev_handle); + libusb_device *(LIBUSB_CALL *get_device)(libusb_device_handle *dev_handle); + int (LIBUSB_CALL *claim_interface)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *release_interface)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *kernel_driver_active)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *detach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *attach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); + int (LIBUSB_CALL *set_interface_alt_setting)(libusb_device_handle *dev, int interface_number, int alternate_setting); + struct libusb_transfer * (LIBUSB_CALL *alloc_transfer)(int iso_packets); + int (LIBUSB_CALL *submit_transfer)(struct libusb_transfer *transfer); + int (LIBUSB_CALL *cancel_transfer)(struct libusb_transfer *transfer); + void (LIBUSB_CALL *free_transfer)(struct libusb_transfer *transfer); + int (LIBUSB_CALL *control_transfer)( + libusb_device_handle *dev_handle, + uint8_t request_type, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + unsigned char *data, + uint16_t wLength, + unsigned int timeout + ); + int (LIBUSB_CALL *interrupt_transfer)( + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *actual_length, + unsigned int timeout + ); + int (LIBUSB_CALL *bulk_transfer)( + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *transferred, + unsigned int timeout + ); + int (LIBUSB_CALL *handle_events)(libusb_context *ctx); + int (LIBUSB_CALL *handle_events_completed)(libusb_context *ctx, int *completed); + const char * (LIBUSB_CALL *error_name)(int errcode); +/* *INDENT-ON* */ // clang-format on + +} SDL_LibUSBContext; + +extern bool SDL_InitLibUSB(SDL_LibUSBContext **ctx); +extern void SDL_QuitLibUSB(void); + +#endif // HAVE_LIBUSB diff --git a/src/process/windows/SDL_windowsprocess.c b/src/process/windows/SDL_windowsprocess.c index 65d8a394a0..cae6cfe1fc 100644 --- a/src/process/windows/SDL_windowsprocess.c +++ b/src/process/windows/SDL_windowsprocess.c @@ -520,8 +520,31 @@ done: return result; } +static BOOL CALLBACK terminate_app(HWND hwnd, LPARAM lparam) +{ + DWORD current_proc_id = 0, *term_info = (DWORD *) lparam; + GetWindowThreadProcessId(hwnd, ¤t_proc_id); + if (current_proc_id == term_info[0] && PostMessage(hwnd, WM_CLOSE, 0, 0)) { + term_info[1]++; + } + return TRUE; +} + bool SDL_SYS_KillProcess(SDL_Process *process, bool force) { + if (!force) { + // term_info[0] is the process ID, term_info[1] is number of successful tries + DWORD term_info[2]; + term_info[0] = process->internal->process_information.dwProcessId; + term_info[1] = 0; + EnumWindows(terminate_app, (LPARAM) &term_info); + if (term_info[1] || PostThreadMessage(process->internal->process_information.dwThreadId, WM_CLOSE, 0, 0)) { + return true; + } + if (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, term_info[0])) { + return true; + } + } if (!TerminateProcess(process->internal->process_information.hProcess, 1)) { return WIN_SetError("TerminateProcess failed"); } diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index b9c15f8b34..303d883b58 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -2797,94 +2797,6 @@ bool SDL_GetRenderLogicalPresentationRect(SDL_Renderer *renderer, SDL_FRect *rec return true; } -static void SDL_RenderLogicalBorders(SDL_Renderer *renderer, const SDL_FRect *dst) -{ - const SDL_RenderViewState *view = renderer->view; - - if (dst->x > 0.0f || dst->y > 0.0f) { - SDL_BlendMode saved_blend_mode = renderer->blendMode; - SDL_FColor saved_color = renderer->color; - - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); - SDL_SetRenderDrawColorFloat(renderer, 0.0f, 0.0f, 0.0f, 1.0f); - - if (dst->x > 0.0f) { - SDL_FRect rect; - - rect.x = 0.0f; - rect.y = 0.0f; - rect.w = dst->x; - rect.h = (float)view->pixel_h; - SDL_RenderFillRect(renderer, &rect); - - rect.x = dst->x + dst->w; - rect.w = (float)view->pixel_w - rect.x; - SDL_RenderFillRect(renderer, &rect); - } - - if (dst->y > 0.0f) { - SDL_FRect rect; - - rect.x = 0.0f; - rect.y = 0.0f; - rect.w = (float)view->pixel_w; - rect.h = dst->y; - SDL_RenderFillRect(renderer, &rect); - - rect.y = dst->y + dst->h; - rect.h = (float)view->pixel_h - rect.y; - SDL_RenderFillRect(renderer, &rect); - } - - SDL_SetRenderDrawBlendMode(renderer, saved_blend_mode); - SDL_SetRenderDrawColorFloat(renderer, saved_color.r, saved_color.g, saved_color.b, saved_color.a); - } -} - -static void SDL_RenderLogicalPresentation(SDL_Renderer *renderer) -{ - SDL_assert(renderer->view == &renderer->main_view); - - SDL_RenderViewState *view = &renderer->main_view; - const SDL_RendererLogicalPresentation mode = view->logical_presentation_mode; - if (mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) { - // save off some state we're going to trample. - const int logical_w = view->logical_w; - const int logical_h = view->logical_h; - const float scale_x = view->scale.x; - const float scale_y = view->scale.y; - const bool clipping_enabled = view->clipping_enabled; - SDL_Rect orig_viewport, orig_cliprect; - const SDL_FRect logical_dst_rect = view->logical_dst_rect; - - SDL_copyp(&orig_viewport, &view->viewport); - if (clipping_enabled) { - SDL_copyp(&orig_cliprect, &view->clip_rect); - } - - // trample some state. - SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, SDL_LOGICAL_PRESENTATION_DISABLED); - SDL_SetRenderViewport(renderer, NULL); - if (clipping_enabled) { - SDL_SetRenderClipRect(renderer, NULL); - } - SDL_SetRenderScale(renderer, 1.0f, 1.0f); - - // draw the borders. - SDL_RenderLogicalBorders(renderer, &logical_dst_rect); - - // now set everything back. - view->logical_presentation_mode = mode; - SDL_SetRenderViewport(renderer, &orig_viewport); - if (clipping_enabled) { - SDL_SetRenderClipRect(renderer, &orig_cliprect); - } - SDL_SetRenderScale(renderer, scale_x, scale_y); - - SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, mode); - } -} - static bool SDL_RenderVectorFromWindow(SDL_Renderer *renderer, float window_dx, float window_dy, float *dx, float *dy) { // Convert from window coordinates to pixels within the window @@ -5401,8 +5313,6 @@ bool SDL_RenderPresent(SDL_Renderer *renderer) return SDL_SetError("You can't present on a render target"); } - SDL_RenderLogicalPresentation(renderer); - if (renderer->transparent_window) { SDL_RenderApplyWindowShape(renderer); } @@ -5948,23 +5858,17 @@ bool SDL_GetDefaultTextureScaleMode(SDL_Renderer *renderer, SDL_ScaleMode *scale return true; } -SDL_GPURenderState *SDL_CreateGPURenderState(SDL_Renderer *renderer, SDL_GPURenderStateDesc *desc) +SDL_GPURenderState *SDL_CreateGPURenderState(SDL_Renderer *renderer, SDL_GPURenderStateCreateInfo *createinfo) { CHECK_RENDERER_MAGIC(renderer, NULL); - if (!desc) { - SDL_InvalidParamError("desc"); + if (!createinfo) { + SDL_InvalidParamError("createinfo"); return NULL; } - if (desc->version < sizeof(*desc)) { - // Update this to handle older versions of this interface - SDL_SetError("Invalid desc, should be initialized with SDL_INIT_INTERFACE()"); - return NULL; - } - - if (!desc->fragment_shader) { - SDL_SetError("desc->fragment_shader is required"); + if (!createinfo->fragment_shader) { + SDL_SetError("A fragment_shader is required"); return NULL; } @@ -5980,36 +5884,36 @@ SDL_GPURenderState *SDL_CreateGPURenderState(SDL_Renderer *renderer, SDL_GPURend } state->renderer = renderer; - state->fragment_shader = desc->fragment_shader; + state->fragment_shader = createinfo->fragment_shader; - if (desc->num_sampler_bindings > 0) { - state->sampler_bindings = (SDL_GPUTextureSamplerBinding *)SDL_calloc(desc->num_sampler_bindings, sizeof(*state->sampler_bindings)); + if (createinfo->num_sampler_bindings > 0) { + state->sampler_bindings = (SDL_GPUTextureSamplerBinding *)SDL_calloc(createinfo->num_sampler_bindings, sizeof(*state->sampler_bindings)); if (!state->sampler_bindings) { SDL_DestroyGPURenderState(state); return NULL; } - SDL_memcpy(state->sampler_bindings, desc->sampler_bindings, desc->num_sampler_bindings * sizeof(*state->sampler_bindings)); - state->num_sampler_bindings = desc->num_sampler_bindings; + SDL_memcpy(state->sampler_bindings, createinfo->sampler_bindings, createinfo->num_sampler_bindings * sizeof(*state->sampler_bindings)); + state->num_sampler_bindings = createinfo->num_sampler_bindings; } - if (desc->num_storage_textures > 0) { - state->storage_textures = (SDL_GPUTexture **)SDL_calloc(desc->num_storage_textures, sizeof(*state->storage_textures)); + if (createinfo->num_storage_textures > 0) { + state->storage_textures = (SDL_GPUTexture **)SDL_calloc(createinfo->num_storage_textures, sizeof(*state->storage_textures)); if (!state->storage_textures) { SDL_DestroyGPURenderState(state); return NULL; } - SDL_memcpy(state->storage_textures, desc->storage_textures, desc->num_storage_textures * sizeof(*state->storage_textures)); - state->num_storage_textures = desc->num_storage_textures; + SDL_memcpy(state->storage_textures, createinfo->storage_textures, createinfo->num_storage_textures * sizeof(*state->storage_textures)); + state->num_storage_textures = createinfo->num_storage_textures; } - if (desc->num_storage_buffers > 0) { - state->storage_buffers = (SDL_GPUBuffer **)SDL_calloc(desc->num_storage_buffers, sizeof(*state->storage_buffers)); + if (createinfo->num_storage_buffers > 0) { + state->storage_buffers = (SDL_GPUBuffer **)SDL_calloc(createinfo->num_storage_buffers, sizeof(*state->storage_buffers)); if (!state->storage_buffers) { SDL_DestroyGPURenderState(state); return NULL; } - SDL_memcpy(state->storage_buffers, desc->storage_buffers, desc->num_storage_buffers * sizeof(*state->storage_buffers)); - state->num_storage_buffers = desc->num_storage_buffers; + SDL_memcpy(state->storage_buffers, createinfo->storage_buffers, createinfo->num_storage_buffers * sizeof(*state->storage_buffers)); + state->num_storage_buffers = createinfo->num_storage_buffers; } return state; diff --git a/src/render/direct3d11/SDL_render_d3d11.c b/src/render/direct3d11/SDL_render_d3d11.c index 12fd5abc45..39fc94dbe8 100644 --- a/src/render/direct3d11/SDL_render_d3d11.c +++ b/src/render/direct3d11/SDL_render_d3d11.c @@ -30,7 +30,11 @@ #include "../../video/SDL_pixels_c.h" #include +#ifdef HAVE_DXGI1_5_H +#include +#else #include +#endif #include #include "SDL_shaders_d3d11.h" @@ -157,6 +161,7 @@ typedef struct ID3D11DeviceContext1 *d3dContext; IDXGISwapChain1 *swapChain; DXGI_SWAP_EFFECT swapEffect; + UINT swapChainFlags; UINT syncInterval; UINT presentFlags; ID3D11RenderTargetView *mainRenderTargetView; @@ -206,6 +211,9 @@ typedef struct #pragma GCC diagnostic ignored "-Wunused-const-variable" #endif +#ifdef HAVE_DXGI1_5_H +static const GUID SDL_IID_IDXGIFactory5 = { 0x7632e1f5, 0xee65, 0x4dca, { 0x87, 0xfd, 0x84, 0xcd, 0x75, 0xf8, 0x83, 0x8d } }; +#endif static const GUID SDL_IID_IDXGIFactory2 = { 0x50c83a1c, 0xe072, 0x4c48, { 0x87, 0xb0, 0x36, 0x30, 0xfa, 0x36, 0xa6, 0xd0 } }; static const GUID SDL_IID_IDXGIDevice1 = { 0x77db970f, 0x6276, 0x48ba, { 0xba, 0x28, 0x07, 0x01, 0x43, 0xb4, 0x39, 0x2c } }; static const GUID SDL_IID_ID3D11Texture2D = { 0x6f15aaf2, 0xd208, 0x4e89, { 0x9a, 0xb4, 0x48, 0x95, 0x35, 0xd3, 0x4f, 0x9c } }; @@ -511,6 +519,9 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) HRESULT result = S_OK; UINT creationFlags = 0; bool createDebug; +#ifdef HAVE_DXGI1_5_H + IDXGIFactory5 *dxgiFactory5 = NULL; +#endif /* This array defines the set of DirectX hardware feature levels this app will support. * Note the ordering should be preserved. @@ -601,6 +612,20 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) goto done; } +#ifdef HAVE_DXGI1_5_H + // Check for tearing support, which requires the IDXGIFactory5 interface. + data->swapChainFlags = 0; + result = IDXGIFactory2_QueryInterface(data->dxgiFactory, &SDL_IID_IDXGIFactory5, (void **)&dxgiFactory5); + if (SUCCEEDED(result)) { + BOOL allowTearing = FALSE; + result = IDXGIFactory5_CheckFeatureSupport(dxgiFactory5, DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing)); + if (SUCCEEDED(result) && allowTearing) { + data->swapChainFlags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + } + IDXGIFactory5_Release(dxgiFactory5); + } +#endif // HAVE_DXGI1_5_H + // FIXME: Should we use the default adapter? result = IDXGIFactory2_EnumAdapters(data->dxgiFactory, 0, &data->dxgiAdapter); if (FAILED(result)) { @@ -870,7 +895,7 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h) } else { swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Windows Store apps must use this SwapEffect. } - swapChainDesc.Flags = 0; + swapChainDesc.Flags = data->swapChainFlags; if (coreWindow) { result = IDXGIFactory2_CreateSwapChainForCoreWindow(data->dxgiFactory, @@ -956,6 +981,28 @@ static void D3D11_ReleaseMainRenderTargetView(SDL_Renderer *renderer) SAFE_RELEASE(data->mainRenderTargetView); } +static void D3D11_UpdatePresentFlags(SDL_Renderer *renderer) +{ + D3D11_RenderData *data = (D3D11_RenderData *)renderer->internal; + + if (data->syncInterval > 0) { + data->presentFlags = 0; + } else { + data->presentFlags = DXGI_PRESENT_DO_NOT_WAIT; +#ifdef HAVE_DXGI1_5_H + // Present tearing requires sync interval 0, a swap chain flag, and not in exclusive fullscreen mode. + if ((data->swapChainFlags & DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING)) { + HRESULT result = S_OK; + BOOL fullscreenState = FALSE; + result = IDXGISwapChain_GetFullscreenState(data->swapChain, &fullscreenState, NULL); + if (SUCCEEDED(result) && !fullscreenState) { + data->presentFlags = DXGI_PRESENT_ALLOW_TEARING; + } + } +#endif // HAVE_DXGI1_5_H + } +} + // Initialize all resources that change when the window's size changes. static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) { @@ -985,7 +1032,7 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) 0, w, h, DXGI_FORMAT_UNKNOWN, - 0); + data->swapChainFlags); if (FAILED(result)) { WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain::ResizeBuffers"), result); goto done; @@ -997,6 +1044,8 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer) } } + D3D11_UpdatePresentFlags(renderer); + // Set the proper rotation for the swap chain. if (WIN_IsWindows8OrGreater()) { if (data->swapEffect == DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL) { @@ -2663,11 +2712,10 @@ static bool D3D11_SetVSync(SDL_Renderer *renderer, const int vsync) if (vsync > 0) { data->syncInterval = vsync; - data->presentFlags = 0; } else { data->syncInterval = 0; - data->presentFlags = DXGI_PRESENT_DO_NOT_WAIT; } + D3D11_UpdatePresentFlags(renderer); return true; } @@ -2733,6 +2781,7 @@ static bool D3D11_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV21); SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_P010); + data->swapChainFlags = 0; data->syncInterval = 0; data->presentFlags = DXGI_PRESENT_DO_NOT_WAIT; diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c index 81c930237c..7bd048cab0 100644 --- a/src/render/opengl/SDL_render_gl.c +++ b/src/render/opengl/SDL_render_gl.c @@ -442,6 +442,43 @@ static bool convert_format(Uint32 pixel_format, GLint *internalFormat, GLenum *f return true; } +static bool SetTextureScaleMode(GL_RenderData *data, GLenum textype, SDL_ScaleMode scaleMode) +{ + switch (scaleMode) { + case SDL_SCALEMODE_NEAREST: + data->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + data->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + break; + case SDL_SCALEMODE_PIXELART: // Uses linear sampling + case SDL_SCALEMODE_LINEAR: + data->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + data->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + break; + default: + return SDL_SetError("Unknown texture scale mode: %d", scaleMode); + } + return true; +} + +static GLint TranslateAddressMode(SDL_TextureAddressMode addressMode) +{ + switch (addressMode) { + case SDL_TEXTURE_ADDRESS_CLAMP: + return GL_CLAMP_TO_EDGE; + case SDL_TEXTURE_ADDRESS_WRAP: + return GL_REPEAT; + default: + SDL_assert(!"Unknown texture address mode"); + return GL_CLAMP_TO_EDGE; + } +} + +static void SetTextureAddressMode(GL_RenderData *data, GLenum textype, SDL_TextureAddressMode addressModeU, SDL_TextureAddressMode addressModeV) +{ + data->glTexParameteri(textype, GL_TEXTURE_WRAP_S, TranslateAddressMode(addressModeU)); + data->glTexParameteri(textype, GL_TEXTURE_WRAP_T, TranslateAddressMode(addressModeV)); +} + static bool GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) { GL_RenderData *renderdata = (GL_RenderData *)renderer->internal; @@ -538,11 +575,13 @@ static bool GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_P data->format = format; data->formattype = type; - data->texture_scale_mode = SDL_SCALEMODE_INVALID; - data->texture_address_mode_u = SDL_TEXTURE_ADDRESS_INVALID; - data->texture_address_mode_v = SDL_TEXTURE_ADDRESS_INVALID; + data->texture_scale_mode = texture->scaleMode; + data->texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP; + data->texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP; renderdata->glEnable(textype); renderdata->glBindTexture(textype, data->texture); + SetTextureScaleMode(renderdata, textype, data->texture_scale_mode); + SetTextureAddressMode(renderdata, textype, data->texture_address_mode_u, data->texture_address_mode_v); #ifdef SDL_PLATFORM_MACOS #ifndef GL_TEXTURE_STORAGE_HINT_APPLE #define GL_TEXTURE_STORAGE_HINT_APPLE 0x85BC @@ -574,10 +613,11 @@ static bool GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_P renderdata->glTexImage2D(textype, 0, internalFormat, texture_w, texture_h, 0, format, type, NULL); } - renderdata->glDisable(textype); if (!GL_CheckError("glTexImage2D()", renderer)) { return false; } + SetTextureScaleMode(renderdata, textype, data->texture_scale_mode); + SetTextureAddressMode(renderdata, textype, data->texture_address_mode_u, data->texture_address_mode_v); #ifdef SDL_HAVE_YUV if (texture->format == SDL_PIXELFORMAT_YV12 || @@ -600,11 +640,15 @@ static bool GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_P renderdata->glBindTexture(textype, data->utexture); renderdata->glTexImage2D(textype, 0, internalFormat, (texture_w + 1) / 2, (texture_h + 1) / 2, 0, format, type, NULL); + SetTextureScaleMode(renderdata, textype, data->texture_scale_mode); + SetTextureAddressMode(renderdata, textype, data->texture_address_mode_u, data->texture_address_mode_v); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_OPENGL_TEXTURE_U_NUMBER, data->utexture); renderdata->glBindTexture(textype, data->vtexture); renderdata->glTexImage2D(textype, 0, internalFormat, (texture_w + 1) / 2, (texture_h + 1) / 2, 0, format, type, NULL); + SetTextureScaleMode(renderdata, textype, data->texture_scale_mode); + SetTextureAddressMode(renderdata, textype, data->texture_address_mode_u, data->texture_address_mode_v); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_OPENGL_TEXTURE_V_NUMBER, data->vtexture); } @@ -621,6 +665,8 @@ static bool GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_P renderdata->glBindTexture(textype, data->utexture); renderdata->glTexImage2D(textype, 0, GL_LUMINANCE_ALPHA, (texture_w + 1) / 2, (texture_h + 1) / 2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, NULL); + SetTextureScaleMode(renderdata, textype, data->texture_scale_mode); + SetTextureAddressMode(renderdata, textype, data->texture_address_mode_u, data->texture_address_mode_v); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_OPENGL_TEXTURE_UV_NUMBER, data->utexture); } #endif @@ -660,6 +706,8 @@ static bool GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_P } #endif // SDL_HAVE_YUV + renderdata->glDisable(textype); + return GL_CheckError("", renderer); } @@ -1083,43 +1131,6 @@ static bool SetDrawState(GL_RenderData *data, const SDL_RenderCommand *cmd, cons return true; } -static bool SetTextureScaleMode(GL_RenderData *data, GLenum textype, SDL_ScaleMode scaleMode) -{ - switch (scaleMode) { - case SDL_SCALEMODE_NEAREST: - data->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - data->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - break; - case SDL_SCALEMODE_PIXELART: // Uses linear sampling - case SDL_SCALEMODE_LINEAR: - data->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - data->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - break; - default: - return SDL_SetError("Unknown texture scale mode: %d", scaleMode); - } - return true; -} - -static GLint TranslateAddressMode(SDL_TextureAddressMode addressMode) -{ - switch (addressMode) { - case SDL_TEXTURE_ADDRESS_CLAMP: - return GL_CLAMP_TO_EDGE; - case SDL_TEXTURE_ADDRESS_WRAP: - return GL_REPEAT; - default: - SDL_assert(!"Unknown texture address mode"); - return GL_CLAMP_TO_EDGE; - } -} - -static void SetTextureAddressMode(GL_RenderData *data, GLenum textype, SDL_TextureAddressMode addressModeU, SDL_TextureAddressMode addressModeV) -{ - data->glTexParameteri(textype, GL_TEXTURE_WRAP_S, TranslateAddressMode(addressModeU)); - data->glTexParameteri(textype, GL_TEXTURE_WRAP_T, TranslateAddressMode(addressModeV)); -} - static bool SetCopyState(GL_RenderData *data, const SDL_RenderCommand *cmd) { SDL_Texture *texture = cmd->data.draw.texture; @@ -1666,7 +1677,7 @@ static bool GL_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_Pr SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, RENDERER_CONTEXT_MAJOR); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, RENDERER_CONTEXT_MINOR); - if (!SDL_RecreateWindow(window, (window_flags & ~(SDL_WINDOW_VULKAN | SDL_WINDOW_METAL)) | SDL_WINDOW_OPENGL)) { + if (!SDL_ReconfigureWindow(window, (window_flags & ~(SDL_WINDOW_VULKAN | SDL_WINDOW_METAL)) | SDL_WINDOW_OPENGL)) { goto error; } } diff --git a/src/render/opengles2/SDL_render_gles2.c b/src/render/opengles2/SDL_render_gles2.c index 4cec2f21e0..8854c524fa 100644 --- a/src/render/opengles2/SDL_render_gles2.c +++ b/src/render/opengles2/SDL_render_gles2.c @@ -1658,9 +1658,9 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD data->texture_u = 0; data->texture_v = 0; #endif - data->texture_scale_mode = SDL_SCALEMODE_INVALID; - data->texture_address_mode_u = SDL_TEXTURE_ADDRESS_INVALID; - data->texture_address_mode_v = SDL_TEXTURE_ADDRESS_INVALID; + data->texture_scale_mode = texture->scaleMode; + data->texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP; + data->texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP; // Allocate a blob for image renderdata if (texture->access == SDL_TEXTUREACCESS_STREAMING) { @@ -1707,6 +1707,13 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD renderdata->glActiveTexture(GL_TEXTURE2); renderdata->glBindTexture(data->texture_type, data->texture_v); renderdata->glTexImage2D(data->texture_type, 0, format, (texture->w + 1) / 2, (texture->h + 1) / 2, 0, format, type, NULL); + if (!GL_CheckError("glTexImage2D()", renderer)) { + SDL_free(data->pixel_data); + SDL_free(data); + return false; + } + SetTextureScaleMode(renderdata, data->texture_type, data->texture_scale_mode); + SetTextureAddressMode(renderdata, data->texture_type, data->texture_address_mode_u, data->texture_address_mode_v); SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_V_NUMBER, data->texture_v); data->texture_u = (GLuint)SDL_GetNumberProperty(create_props, SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_U_NUMBER, 0); @@ -1728,6 +1735,8 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD SDL_free(data); return false; } + SetTextureScaleMode(renderdata, data->texture_type, data->texture_scale_mode); + SetTextureAddressMode(renderdata, data->texture_type, data->texture_address_mode_u, data->texture_address_mode_v); SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_U_NUMBER, data->texture_u); if (!SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8)) { @@ -1755,6 +1764,8 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD SDL_free(data); return false; } + SetTextureScaleMode(renderdata, data->texture_type, data->texture_scale_mode); + SetTextureAddressMode(renderdata, data->texture_type, data->texture_address_mode_u, data->texture_address_mode_v); SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_UV_NUMBER, data->texture_u); if (!SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8)) { @@ -1785,6 +1796,8 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD return false; } } + SetTextureScaleMode(renderdata, data->texture_type, data->texture_scale_mode); + SetTextureAddressMode(renderdata, data->texture_type, data->texture_address_mode_u, data->texture_address_mode_v); SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_NUMBER, data->texture); SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_TARGET_NUMBER, data->texture_type); @@ -2176,7 +2189,7 @@ static bool GLES2_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, RENDERER_CONTEXT_MAJOR); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, RENDERER_CONTEXT_MINOR); - if (!SDL_RecreateWindow(window, (window_flags & ~(SDL_WINDOW_VULKAN | SDL_WINDOW_METAL)) | SDL_WINDOW_OPENGL)) { + if (!SDL_ReconfigureWindow(window, (window_flags & ~(SDL_WINDOW_VULKAN | SDL_WINDOW_METAL)) | SDL_WINDOW_OPENGL)) { goto error; } } diff --git a/src/storage/steam/SDL_steamstorage.c b/src/storage/steam/SDL_steamstorage.c index 4c92e4260b..674c528ce8 100644 --- a/src/storage/steam/SDL_steamstorage.c +++ b/src/storage/steam/SDL_steamstorage.c @@ -23,6 +23,23 @@ #include "../SDL_sysstorage.h" +#if defined(_WIN64) +#define SDL_DRIVER_STEAMAPI_DYNAMIC "steam_api64.dll" +#elif defined(_WIN32) +#define SDL_DRIVER_STEAMAPI_DYNAMIC "steam_api.dll" +#elif defined(__APPLE__) +#define SDL_DRIVER_STEAMAPI_DYNAMIC "libsteam_api.dylib" +#else +#define SDL_DRIVER_STEAMAPI_DYNAMIC "libsteam_api.so" +#endif + +SDL_ELF_NOTE_DLOPEN( + "storage-steam", + "Support for Steam user storage", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_DRIVER_STEAMAPI_DYNAMIC +); + // !!! FIXME: Async API can use SteamRemoteStorage_ReadFileAsync // !!! FIXME: Async API can use SteamRemoteStorage_WriteFileAsync @@ -154,17 +171,7 @@ static SDL_Storage *STEAM_User_Create(const char *org, const char *app, SDL_Prop return NULL; } - steam->libsteam_api = SDL_LoadObject( -#if defined(_WIN64) - "steam_api64.dll" -#elif defined(_WIN32) - "steam_api.dll" -#elif defined(__APPLE__) - "libsteam_api.dylib" -#else - "libsteam_api.so" -#endif - ); + steam->libsteam_api = SDL_LoadObject(SDL_DRIVER_STEAMAPI_DYNAMIC); if (steam->libsteam_api == NULL) { SDL_free(steam); return NULL; diff --git a/src/tray/dummy/SDL_tray.c b/src/tray/dummy/SDL_tray.c index 766fb92584..0847e34357 100644 --- a/src/tray/dummy/SDL_tray.c +++ b/src/tray/dummy/SDL_tray.c @@ -21,7 +21,7 @@ #include "SDL_internal.h" -#ifndef SDL_PLATFORM_MACOS +#ifdef SDL_TRAY_DUMMY #include "../SDL_tray_utils.h" @@ -140,4 +140,4 @@ void SDL_DestroyTray(SDL_Tray *tray) { } -#endif // !SDL_PLATFORM_MACOS +#endif // SDL_TRAY_DUMMY diff --git a/src/tray/unix/SDL_tray.c b/src/tray/unix/SDL_tray.c index 874df8b15d..c1b996a252 100644 --- a/src/tray/unix/SDL_tray.c +++ b/src/tray/unix/SDL_tray.c @@ -22,6 +22,7 @@ #include "SDL_internal.h" #include "../SDL_tray_utils.h" +#include "../../video/SDL_stb_c.h" #include #include @@ -184,7 +185,7 @@ static bool new_tmp_filename(SDL_Tray *tray) { static int count = 0; - int would_have_written = SDL_asprintf(&tray->icon_path, "%s/%d.bmp", tray->icon_dir, count++); + int would_have_written = SDL_asprintf(&tray->icon_path, "%s/%d.png", tray->icon_dir, count++); if (would_have_written >= 0) { return true; @@ -289,7 +290,7 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) goto icon_dir_error; } - SDL_SaveBMP(icon, tray->icon_path); + SDL_SavePNG(icon, tray->icon_path); } else { // allocate a dummy icon path SDL_asprintf(&tray->icon_path, " "); @@ -342,7 +343,7 @@ void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon) /* AppIndicator caches the icon files; always change filename to avoid caching */ if (icon && new_tmp_filename(tray)) { - SDL_SaveBMP(icon, tray->icon_path); + SDL_SavePNG(icon, tray->icon_path); app_indicator_set_icon(tray->indicator, tray->icon_path); } else { SDL_free(tray->icon_path); diff --git a/src/tray/windows/SDL_tray.c b/src/tray/windows/SDL_tray.c index 15021ac798..31da4d2ff7 100644 --- a/src/tray/windows/SDL_tray.c +++ b/src/tray/windows/SDL_tray.c @@ -23,13 +23,10 @@ #include "../SDL_tray_utils.h" #include "../../core/windows/SDL_windows.h" -#include "../../video/windows/SDL_windowswindow.h" #include #include -#include "../../video/windows/SDL_surface_utils.h" - #ifndef NOTIFYICON_VERSION_4 #define NOTIFYICON_VERSION_4 4 #endif @@ -115,7 +112,7 @@ LRESULT CALLBACK TrayWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lPar case WM_TRAYICON: if (LOWORD(lParam) == WM_CONTEXTMENU || LOWORD(lParam) == WM_LBUTTONUP) { SetForegroundWindow(hwnd); - + if (tray->menu) { TrackPopupMenu(tray->menu->hMenu, TPM_BOTTOMALIGN | TPM_RIGHTALIGN, GET_X_LPARAM(wParam), GET_Y_LPARAM(wParam), 0, hwnd, NULL); } @@ -247,7 +244,7 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) SDL_free(tooltipw); if (icon) { - tray->nid.hIcon = CreateIconFromSurface(icon); + tray->nid.hIcon = WIN_CreateIconFromSurface(icon); if (!tray->nid.hIcon) { tray->nid.hIcon = load_default_icon(); @@ -280,7 +277,7 @@ void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon) } if (icon) { - tray->nid.hIcon = CreateIconFromSurface(icon); + tray->nid.hIcon = WIN_CreateIconFromSurface(icon); if (!tray->nid.hIcon) { tray->nid.hIcon = load_default_icon(); diff --git a/src/video/SDL_RLEaccel.c b/src/video/SDL_RLEaccel.c index 890b8250aa..2ca6d7e0db 100644 --- a/src/video/SDL_RLEaccel.c +++ b/src/video/SDL_RLEaccel.c @@ -1434,6 +1434,7 @@ bool SDL_RLESurface(SDL_Surface *surface) // The surface is now accelerated surface->internal_flags |= SDL_INTERNAL_SURFACE_RLEACCEL; + SDL_UpdateSurfaceLockFlag(surface); return true; } @@ -1565,11 +1566,12 @@ void SDL_UnRLESurface(SDL_Surface *surface, bool recode) } } } - surface->map.info.flags &= - ~(SDL_COPY_RLE_COLORKEY | SDL_COPY_RLE_ALPHAKEY); + surface->map.info.flags &= ~(SDL_COPY_RLE_COLORKEY | SDL_COPY_RLE_ALPHAKEY); SDL_free(surface->map.data); surface->map.data = NULL; + + SDL_UpdateSurfaceLockFlag(surface); } } diff --git a/src/video/SDL_blit.c b/src/video/SDL_blit.c index 5ffd1814e6..860a2a9bc2 100644 --- a/src/video/SDL_blit.c +++ b/src/video/SDL_blit.c @@ -191,7 +191,7 @@ bool SDL_CalculateBlit(SDL_Surface *surface, SDL_Surface *dst) #ifdef SDL_HAVE_RLE // Clean everything out to start - if (surface->flags & SDL_INTERNAL_SURFACE_RLEACCEL) { + if (surface->internal_flags & SDL_INTERNAL_SURFACE_RLEACCEL) { SDL_UnRLESurface(surface, true); } #endif diff --git a/src/video/SDL_egl.c b/src/video/SDL_egl.c index 3d4df26073..4cf7712e31 100644 --- a/src/video/SDL_egl.c +++ b/src/video/SDL_egl.c @@ -110,6 +110,37 @@ #define DEFAULT_OGL_ES2 "libGLESv2.so.2" #define DEFAULT_OGL_ES_PVR "libGLES_CM.so.1" #define DEFAULT_OGL_ES "libGLESv1_CM.so.1" + +SDL_ELF_NOTE_DLOPEN( + "egl-opengl", + "Support for OpenGL", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + DEFAULT_OGL, ALT_OGL +); +SDL_ELF_NOTE_DLOPEN( + "egl-egl", + "Support for EGL", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + DEFAULT_EGL +); +SDL_ELF_NOTE_DLOPEN( + "egl-es2", + "Support for EGL ES2", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + DEFAULT_OGL_ES2 +); +SDL_ELF_NOTE_DLOPEN( + "egl-es-pvr", + "Support for EGL ES PVR", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + DEFAULT_OGL_ES_PVR +); +SDL_ELF_NOTE_DLOPEN( + "egl-ogl-es", + "Support for OpenGL ES", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + DEFAULT_OGL_ES +); #endif // SDL_VIDEO_DRIVER_RPI #if defined(SDL_VIDEO_OPENGL) && !defined(SDL_VIDEO_VITA_PVR_OGL) diff --git a/src/video/SDL_stb.c b/src/video/SDL_stb.c index 934c1addff..8b4bd129b9 100644 --- a/src/video/SDL_stb.c +++ b/src/video/SDL_stb.c @@ -21,7 +21,7 @@ #include "SDL_internal.h" #include "SDL_stb_c.h" - +#include "SDL_surface_c.h" // We currently only support JPEG, but we could add other image formats if we wanted #ifdef SDL_HAVE_STB @@ -59,6 +59,13 @@ #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" +#undef memcpy +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define STB_IMAGE_WRITE_STATIC +#define STBI_WRITE_NO_STDIO +#define STBIW_ASSERT SDL_assert +#include "stb_image_write.h" + #undef memset #endif @@ -119,3 +126,68 @@ bool SDL_ConvertPixels_STB(int width, int height, return SDL_SetError("SDL not built with STB image support"); #endif } + +#ifdef SDL_HAVE_STB +static void SDL_STBWriteFunc(void *context, void *data, int size) +{ + SDL_WriteIO(context, data, size); +} +#endif + +bool SDL_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio) +{ +#ifdef SDL_HAVE_STB + bool retval = true; + + // Make sure we have somewhere to save + if (!SDL_SurfaceValid(surface)) { + retval = SDL_InvalidParamError("surface"); + goto done; + } + if (!dst) { + retval = SDL_InvalidParamError("dst"); + goto done; + } + + bool free_surface = false; + if (surface->format != SDL_PIXELFORMAT_ABGR8888) { + surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ABGR8888); + if (!surface) { + retval = false; + goto done; + } + free_surface = true; + } + + if (!stbi_write_png_to_func(SDL_STBWriteFunc, dst, surface->w, surface->h, 4, surface->pixels, surface->pitch)) { + retval = SDL_SetError("Failed to write PNG"); + } + + if (free_surface) { + SDL_DestroySurface(surface); + } + +done: + if (dst && closeio) { + retval = SDL_CloseIO(dst); + } + + return retval; +#else + return SDL_SetError("SDL not built with STB image write support"); +#endif +} + +bool SDL_SavePNG(SDL_Surface *surface, const char *file) +{ +#ifdef SDL_HAVE_STB + SDL_IOStream *stream = SDL_IOFromFile(file, "wb"); + if (!stream) { + return false; + } + + return SDL_SavePNG_IO(surface, stream, true); +#else + return SDL_SetError("SDL not built with STB image write support"); +#endif +} diff --git a/src/video/SDL_stb_c.h b/src/video/SDL_stb_c.h index bf8cc4da53..7ae541380a 100644 --- a/src/video/SDL_stb_c.h +++ b/src/video/SDL_stb_c.h @@ -27,5 +27,7 @@ // Image conversion functions extern bool SDL_ConvertPixels_STB(int width, int height, SDL_PixelFormat src_format, SDL_Colorspace src_colorspace, SDL_PropertiesID src_properties, const void *src, int src_pitch, SDL_PixelFormat dst_format, SDL_Colorspace dst_colorspace, SDL_PropertiesID dst_properties, void *dst, int dst_pitch); +extern bool SDL_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio); +extern bool SDL_SavePNG(SDL_Surface *surface, const char *file); #endif // SDL_stb_c_h_ diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c index a8f8353236..7fe2542f76 100644 --- a/src/video/SDL_surface.c +++ b/src/video/SDL_surface.c @@ -49,7 +49,7 @@ bool SDL_SurfaceValid(SDL_Surface *surface) void SDL_UpdateSurfaceLockFlag(SDL_Surface *surface) { - if (SDL_SurfaceHasRLE(surface)) { + if (surface->internal_flags & SDL_INTERNAL_SURFACE_RLEACCEL) { surface->flags |= SDL_SURFACE_LOCK_NEEDED; } else { surface->flags &= ~SDL_SURFACE_LOCK_NEEDED; @@ -611,7 +611,6 @@ bool SDL_SetSurfaceRLE(SDL_Surface *surface, bool enabled) if (surface->map.info.flags != flags) { SDL_InvalidateMap(&surface->map); } - SDL_UpdateSurfaceLockFlag(surface); return true; } @@ -1760,6 +1759,7 @@ bool SDL_LockSurface(SDL_Surface *surface) if (surface->internal_flags & SDL_INTERNAL_SURFACE_RLEACCEL) { SDL_UnRLESurface(surface, true); surface->internal_flags |= SDL_INTERNAL_SURFACE_RLEACCEL; // save accel'd state + SDL_UpdateSurfaceLockFlag(surface); } #endif } @@ -1874,14 +1874,23 @@ bool SDL_FlipSurface(SDL_Surface *surface, SDL_FlipMode flip) return true; } + bool result = true; switch (flip) { case SDL_FLIP_HORIZONTAL: - return SDL_FlipSurfaceHorizontal(surface); + result = SDL_FlipSurfaceHorizontal(surface); + break; case SDL_FLIP_VERTICAL: - return SDL_FlipSurfaceVertical(surface); + result = SDL_FlipSurfaceVertical(surface); + break; + case SDL_FLIP_HORIZONTAL_AND_VERTICAL: + result &= SDL_FlipSurfaceHorizontal(surface); + result &= SDL_FlipSurfaceVertical(surface); + break; default: - return SDL_InvalidParamError("flip"); + result = SDL_InvalidParamError("flip"); + break; } + return result; } SDL_Surface *SDL_ConvertSurfaceAndColorspace(SDL_Surface *surface, SDL_PixelFormat format, SDL_Palette *palette, SDL_Colorspace colorspace, SDL_PropertiesID props) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 1f05ab1c04..a225205a78 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -314,6 +314,7 @@ struct SDL_VideoDevice bool (*ApplyWindowProgress)(SDL_VideoDevice *_this, SDL_Window *window); bool (*SetWindowFocusable)(SDL_VideoDevice *_this, SDL_Window *window, bool focusable); bool (*SyncWindow)(SDL_VideoDevice *_this, SDL_Window *window); + bool (*ReconfigureWindow)(SDL_VideoDevice *_this, SDL_Window *window, SDL_WindowFlags flags); /* * * */ /* @@ -575,6 +576,7 @@ extern void SDL_SetWindowSafeAreaInsets(SDL_Window *window, int left, int right, extern void SDL_GL_DeduceMaxSupportedESProfile(int *major, int *minor); extern bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags); +extern bool SDL_ReconfigureWindow(SDL_Window *window, SDL_WindowFlags flags); extern bool SDL_HasWindows(void); extern void SDL_RelativeToGlobalForWindow(SDL_Window *window, int rel_x, int rel_y, int *abs_x, int *abs_y); extern void SDL_GlobalToRelativeForWindow(SDL_Window *window, int abs_x, int abs_y, int *rel_x, int *rel_y); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 0c2e2f1ebb..971204380f 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -451,6 +451,7 @@ static bool SDL_CreateWindowTexture(SDL_VideoDevice *_this, SDL_Window *window, // codechecker_false_positive [Malloc] Static analyzer doesn't realize allocated `data` is saved to SDL_PROP_WINDOW_TEXTUREDATA_POINTER and not leaked here. return false; // NOLINT(clang-analyzer-unix.Malloc) } + SDL_SetTextureBlendMode(data->texture, SDL_BLENDMODE_NONE); // Create framebuffer data data->bytes_per_pixel = SDL_BYTESPERPIXEL(*format); @@ -2637,6 +2638,71 @@ SDL_Window *SDL_CreatePopupWindow(SDL_Window *parent, int offset_x, int offset_y return window; } +static bool SDL_ReconfigureWindowInternal(SDL_Window *window, SDL_WindowFlags flags) +{ + bool loaded_opengl = false; + bool loaded_vulkan = false; + + if (!_this->ReconfigureWindow) { + return false; + } + + // Don't attempt to reconfigure external windows. + if (window->flags & SDL_WINDOW_EXTERNAL) { + return false; + } + + // Only attempt to reconfigure if the window has no existing graphics flags. + if (window->flags & (SDL_WINDOW_OPENGL | SDL_WINDOW_METAL | SDL_WINDOW_VULKAN)) { + return false; + } + + const SDL_WindowFlags graphics_flags = flags & (SDL_WINDOW_OPENGL | SDL_WINDOW_METAL | SDL_WINDOW_VULKAN); + if (graphics_flags & (graphics_flags - 1)) { + return SDL_SetError("Conflicting window flags specified"); + } + + if ((flags & SDL_WINDOW_OPENGL) && !_this->GL_CreateContext) { + return SDL_ContextNotSupported("OpenGL"); + } + if ((flags & SDL_WINDOW_VULKAN) && !_this->Vulkan_CreateSurface) { + return SDL_ContextNotSupported("Vulkan"); + } + if ((flags & SDL_WINDOW_METAL) && !_this->Metal_CreateView) { + return SDL_ContextNotSupported("Metal"); + } + + SDL_DestroyWindowSurface(window); + + if (graphics_flags & SDL_WINDOW_OPENGL) { + loaded_opengl = SDL_GL_LoadLibrary(NULL); + if (!loaded_opengl) { + return false; + } + } else if (graphics_flags & SDL_WINDOW_VULKAN) { + loaded_vulkan = SDL_GL_LoadLibrary(NULL); + if (!loaded_vulkan) { + return false; + } + } + + // Try to reconfigure the window for the requested graphics flags. + if (!_this->ReconfigureWindow(_this, window, graphics_flags)) { + if (loaded_opengl) { + SDL_GL_UnloadLibrary(); + } + if (loaded_vulkan) { + SDL_Vulkan_UnloadLibrary(); + } + + return false; + } + + window->flags |= graphics_flags; + + return true; +} + bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags) { bool loaded_opengl = false; @@ -2791,6 +2857,16 @@ bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags) return true; } +bool SDL_ReconfigureWindow(SDL_Window *window, SDL_WindowFlags flags) +{ + // Try to reconfigure the window for the desired flags first, before completely destroying and recreating it. + if (!SDL_ReconfigureWindowInternal(window, flags)) { + return SDL_RecreateWindow(window, flags); + } + + return true; +} + bool SDL_HasWindows(void) { return _this && _this->windows; diff --git a/src/video/cocoa/SDL_cocoamouse.m b/src/video/cocoa/SDL_cocoamouse.m index f8f582972f..fbf2b06ad2 100644 --- a/src/video/cocoa/SDL_cocoamouse.m +++ b/src/video/cocoa/SDL_cocoamouse.m @@ -373,12 +373,12 @@ bool Cocoa_InitMouse(SDL_VideoDevice *_this) { NSPoint location; SDL_Mouse *mouse = SDL_GetMouse(); - SDL_MouseData *internal = (SDL_MouseData *)SDL_calloc(1, sizeof(SDL_MouseData)); - if (internal == NULL) { + SDL_MouseData *data = (SDL_MouseData *)SDL_calloc(1, sizeof(SDL_MouseData)); + if (data == NULL) { return false; } - mouse->internal = internal; + mouse->internal = data; mouse->CreateCursor = Cocoa_CreateCursor; mouse->CreateSystemCursor = Cocoa_CreateSystemCursor; mouse->ShowCursor = Cocoa_ShowCursor; @@ -392,8 +392,8 @@ bool Cocoa_InitMouse(SDL_VideoDevice *_this) SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor()); location = [NSEvent mouseLocation]; - internal->lastMoveX = location.x; - internal->lastMoveY = location.y; + data->lastMoveX = location.x; + data->lastMoveY = location.y; return true; } @@ -585,13 +585,6 @@ void Cocoa_HandleMouseWarp(CGFloat x, CGFloat y) void Cocoa_QuitMouse(SDL_VideoDevice *_this) { - SDL_Mouse *mouse = SDL_GetMouse(); - if (mouse) { - if (mouse->internal) { - SDL_free(mouse->internal); - mouse->internal = NULL; - } - } } #endif // SDL_VIDEO_DRIVER_COCOA diff --git a/src/video/kmsdrm/SDL_kmsdrmdyn.c b/src/video/kmsdrm/SDL_kmsdrmdyn.c index a532ceaf15..af8ffb6324 100644 --- a/src/video/kmsdrm/SDL_kmsdrmdyn.c +++ b/src/video/kmsdrm/SDL_kmsdrmdyn.c @@ -29,6 +29,13 @@ #ifdef SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "video-kmsdrm", + "Support for KMSDRM", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC +); + typedef struct { void *lib; diff --git a/src/video/kmsdrm/SDL_kmsdrmvulkan.c b/src/video/kmsdrm/SDL_kmsdrmvulkan.c index e60af3ff78..b81e85d6c8 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvulkan.c +++ b/src/video/kmsdrm/SDL_kmsdrmvulkan.c @@ -42,6 +42,13 @@ #define DEFAULT_VULKAN "libvulkan.so.1" #endif +SDL_ELF_NOTE_DLOPEN( + "kmsdrm-vulkan", + "Support for Vulkan on KMSDRM", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + DEFAULT_VULKAN +); + bool KMSDRM_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) { VkExtensionProperties *extensions = NULL; diff --git a/src/video/offscreen/SDL_offscreenvulkan.c b/src/video/offscreen/SDL_offscreenvulkan.c index b3dca4acba..ac1a84f102 100644 --- a/src/video/offscreen/SDL_offscreenvulkan.c +++ b/src/video/offscreen/SDL_offscreenvulkan.c @@ -42,6 +42,13 @@ static const char *s_defaultPaths[] = { #endif }; +SDL_ELF_NOTE_DLOPEN( + "offscreen-vulkan", + "Support for offscreen Vulkan", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libvulkan.so.1" +); + #if defined( SDL_PLATFORM_APPLE ) #include diff --git a/src/video/openvr/SDL_openvrvideo.c b/src/video/openvr/SDL_openvrvideo.c index f8bf977264..b163f4df51 100644 --- a/src/video/openvr/SDL_openvrvideo.c +++ b/src/video/openvr/SDL_openvrvideo.c @@ -53,6 +53,19 @@ struct SDL_GLContextState #include #endif +#ifdef SDL_PLATFORM_WINDOWS +#define SDL_OPENVR_DRIVER_DYNAMIC "openvr_api.dll" +#else +#define SDL_OPENVR_DRIVER_DYNAMIC "openvr_api.so" +#endif + +SDL_ELF_NOTE_DLOPEN( + "video-openvr", + "Support for OpenVR video", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_OPENVR_DRIVER_DYNAMIC +); + #define MARKER_ID 0 #define MARKER_STR "vr-marker,frame_end,type,application" @@ -209,8 +222,6 @@ static bool OPENVR_VideoInit(SDL_VideoDevice *_this) } else { display.desktop_mode.refresh_rate = data->oSystem->GetFloatTrackedDeviceProperty(k_unTrackedDeviceIndex_Hmd, ETrackedDeviceProperty_Prop_DisplayFrequency_Float, 0); } - - display.internal = (SDL_DisplayData *)data; display.name = (char *)"OpenVRDisplay"; SDL_AddVideoDisplay(&display, false); @@ -1474,6 +1485,7 @@ static SDL_VideoDevice *OPENVR_CreateDevice(void) { SDL_VideoDevice *device; SDL_VideoData *data; + const char *hint; #ifdef SDL_PLATFORM_WINDOWS SDL_RegisterApp(NULL, 0, NULL); @@ -1495,19 +1507,13 @@ static SDL_VideoDevice *OPENVR_CreateDevice(void) } device->internal = data; - { - const char * hint = SDL_GetHint(SDL_HINT_OPENVR_LIBRARY); - if (hint) - data->openVRLIB = SDL_LoadObject(hint); -#ifdef SDL_PLATFORM_WINDOWS - if (!data->openVRLIB) - data->openVRLIB = SDL_LoadObject("openvr_api.dll"); -#else - if (!data->openVRLIB) - data->openVRLIB = SDL_LoadObject("openvr_api.so"); -#endif + hint = SDL_GetHint(SDL_HINT_OPENVR_LIBRARY); + if (hint) { + data->openVRLIB = SDL_LoadObject(hint); + } + if (!data->openVRLIB) { + data->openVRLIB = SDL_LoadObject(SDL_OPENVR_DRIVER_DYNAMIC); } - if (!data->openVRLIB) { SDL_SetError("Could not open OpenVR API Library"); goto error; @@ -1542,7 +1548,7 @@ static SDL_VideoDevice *OPENVR_CreateDevice(void) SDL_SetError("Could not get interfaces for the OpenVR System (%s), Overlay (%s) and Input (%s) versions", IVRSystem_Version, IVROverlay_Version, IVRInput_Version); } - const char *hint = SDL_GetHint("SDL_OPENVR_INPUT_PROFILE"); + hint = SDL_GetHint("SDL_OPENVR_INPUT_PROFILE"); char *loadpath = 0; EVRInputError err; diff --git a/src/video/psp/SDL_pspmouse_c.h b/src/video/psp/SDL_pspmouse_c.h deleted file mode 100644 index 95d1ff4464..0000000000 --- a/src/video/psp/SDL_pspmouse_c.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - 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_pspvideo.h" - -// Functions to be exported diff --git a/src/video/stb_image_write.h b/src/video/stb_image_write.h new file mode 100644 index 0000000000..2a87ea5d7f --- /dev/null +++ b/src/video/stb_image_write.h @@ -0,0 +1,1735 @@ +/* stb_image_write - v1.16 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + Andrew Kensler + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#if 0 +#include +#endif + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +// STBIWDEF int stbi_write_tga_with_rle; +STBIWDEF int stbi_write_png_compression_level; +STBIWDEF int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBIW_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +#if 0 +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); +#endif + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#if 0 +#include +#include +#include +#include +#endif + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +// static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; +#if 0 +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} +#endif + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; +#if 0 +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) { + j_end = -1; j = y-1; + } else { + j_end = y; j = 0; + } + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + if (comp != 4) { + // write RGB bitmap + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header + } else { + // RGBA bitmaps need a v4 header + // use BI_BITFIELDS mode with 32bpp and alpha mask + // (straight BI_RGB with alpha mask doesn't work in most readers) + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, + "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", + 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header + 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header + } +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +#if 0 +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +#ifndef STBI_WRITE_NO_STDIO + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_LIB_EXT1__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) { best=d; bestloc=hlist[j]; } + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + // store uncompressed instead if compression was worse + if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { + stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 + for (j = 0; j < data_len;) { + int blocklen = data_len - j; + if (blocklen > 32767) blocklen = 32767; + stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression + stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN + stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN + stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); + SDL_memcpy(out+stbiw__sbn(out), data+j, blocklen); + stbiw__sbn(out) += blocklen; + j += blocklen; + } + } + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + SDL_memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + +#if 0 +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.16 (2021-07-11) + make Deflate code emit uncompressed blocks when it would otherwise expand + support writing BMPs with alpha channel + 1.15 (2020-07-13) unknown + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/video/wayland/SDL_waylanddatamanager.c b/src/video/wayland/SDL_waylanddatamanager.c index 61928f4f6b..53375a09b5 100644 --- a/src/video/wayland/SDL_waylanddatamanager.c +++ b/src/video/wayland/SDL_waylanddatamanager.c @@ -42,6 +42,48 @@ */ #define PIPE_TIMEOUT_NS SDL_MS_TO_NS(14) +/* sigtimedwait() is an optional part of POSIX.1-2001, and OpenBSD doesn't implement it. + * Based on https://comp.unix.programmer.narkive.com/rEDH0sPT/sigtimedwait-implementation + */ +#ifndef HAVE_SIGTIMEDWAIT +#include +#include +static int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout) +{ + struct timespec elapsed = { 0 }, rem = { 0 }; + sigset_t pending; + + do { + // Check the pending signals, and call sigwait if there is at least one of interest in the set. + sigpending(&pending); + for (int signo = 1; signo < NSIG; ++signo) { + if (sigismember(set, signo) && sigismember(&pending, signo)) { + if (!sigwait(set, &signo)) { + if (info) { + SDL_memset(info, 0, sizeof *info); + info->si_signo = signo; + } + return signo; + } else { + return -1; + } + } + } + + if (timeout->tv_sec || timeout->tv_nsec) { + long ns = 20000000L; // 2/100ths of a second + nanosleep(&(struct timespec){ 0, ns }, &rem); + ns -= rem.tv_nsec; + elapsed.tv_sec += (elapsed.tv_nsec + ns) / 1000000000L; + elapsed.tv_nsec = (elapsed.tv_nsec + ns) % 1000000000L; + } + } while (elapsed.tv_sec < timeout->tv_sec || (elapsed.tv_sec == timeout->tv_sec && elapsed.tv_nsec < timeout->tv_nsec)); + + errno = EAGAIN; + return -1; +} +#endif + static ssize_t write_pipe(int fd, const void *buffer, size_t total_length, size_t *pos) { int ready = 0; @@ -77,7 +119,7 @@ static ssize_t write_pipe(int fd, const void *buffer, size_t total_length, size_ } } - sigtimedwait(&sig_set, 0, &zerotime); + sigtimedwait(&sig_set, NULL, &zerotime); #ifdef SDL_THREADS_DISABLED sigprocmask(SIG_SETMASK, &old_sig_set, NULL); diff --git a/src/video/wayland/SDL_waylanddyn.c b/src/video/wayland/SDL_waylanddyn.c index 7d6d42f3f5..e45a96fb53 100644 --- a/src/video/wayland/SDL_waylanddyn.c +++ b/src/video/wayland/SDL_waylanddyn.c @@ -28,6 +28,45 @@ #ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC +SDL_ELF_NOTE_DLOPEN( + "wayland", + "Support for Wayland video", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC +); +#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_EGL +SDL_ELF_NOTE_DLOPEN( + "wayland", + "Support for Wayland video", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_EGL +); +#endif +#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_CURSOR +SDL_ELF_NOTE_DLOPEN( + "wayland", + "Support for Wayland video", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_CURSOR +); +#endif +#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON +SDL_ELF_NOTE_DLOPEN( + "wayland", + "Support for Wayland video", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON +); +#endif +#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR +SDL_ELF_NOTE_DLOPEN( + "wayland", + "Support for Wayland video", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR +); +#endif + typedef struct { SDL_SharedObject *lib; diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 43f31552cd..c0bf4f03cc 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -1243,8 +1243,8 @@ static void relative_pointer_handle_relative_motion(void *data, seat->pointer.pending_frame.have_relative = true; seat->pointer.pending_frame.relative.dx = dx; seat->pointer.pending_frame.relative.dy = dy; - seat->pointer.pending_frame.relative.dx_unaccel = dx; - seat->pointer.pending_frame.relative.dy_unaccel = dy; + seat->pointer.pending_frame.relative.dx_unaccel = dx_unaccel; + seat->pointer.pending_frame.relative.dy_unaccel = dy_unaccel; seat->pointer.pending_frame.timestamp_ns = Wayland_AdjustEventTimestampBase(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo)); if (wl_pointer_get_version(seat->pointer.wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) { @@ -2258,7 +2258,6 @@ static void Wayland_SeatDestroyKeyboard(SDL_WaylandSeat *seat, bool send_event) if (seat->keyboard.sdl_keymap) { if (seat->keyboard.xkb.current_layout < seat->keyboard.xkb.num_layouts && seat->keyboard.sdl_keymap[seat->keyboard.xkb.current_layout] == SDL_GetCurrentKeymap(true)) { - SDL_SetKeymap(NULL, false); SDL_SetModState(SDL_KMOD_NONE); } for (xkb_layout_index_t i = 0; i < seat->keyboard.xkb.num_layouts; ++i) { @@ -3764,9 +3763,6 @@ void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat) if (confine_rect) { wl_region_destroy(confine_rect); } - - // Commit the new confinement region immediately. - wl_surface_commit(w->surface); } } } diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index f32124555a..1c74dd368b 100644 --- a/src/video/wayland/SDL_waylandmouse.c +++ b/src/video/wayland/SDL_waylandmouse.c @@ -889,14 +889,14 @@ static bool Wayland_WarpMouseRelative(SDL_Window *window, float x, float y) SDL_WindowData *wind = window->internal; SDL_WaylandSeat *seat; - if (d->pointer_constraints) { + if (d->wp_pointer_warp_v1 || d->pointer_constraints) { wl_list_for_each (seat, &d->seat_list, link) { if (wind == seat->pointer.focus) { Wayland_SeatWarpMouse(seat, wind, x, y); } } } else { - return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required zwp_pointer_confinement_v1 protocol"); + return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required wp_pointer_warp_v1 or zwp_pointer_confinement_v1 protocol"); } return true; @@ -908,7 +908,7 @@ static bool Wayland_WarpMouseGlobal(float x, float y) SDL_VideoData *d = vd->internal; SDL_WaylandSeat *seat; - if (d->pointer_constraints) { + if (d->wp_pointer_warp_v1 || d->pointer_constraints) { wl_list_for_each (seat, &d->seat_list, link) { SDL_WindowData *wind = seat->pointer.focus ? seat->pointer.focus : seat->keyboard.focus; @@ -929,7 +929,7 @@ static bool Wayland_WarpMouseGlobal(float x, float y) } } } else { - return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required zwp_pointer_confinement_v1 protocol"); + return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required wp_pointer_warp_v1 or zwp_pointer_confinement_v1 protocol"); } return true; diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 68fb9961c8..3286f8aec7 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -660,6 +660,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols) device->ShowWindowSystemMenu = Wayland_ShowWindowSystemMenu; device->SyncWindow = Wayland_SyncWindow; device->SetWindowFocusable = Wayland_SetWindowFocusable; + device->ReconfigureWindow = Wayland_ReconfigureWindow; #ifdef SDL_USE_LIBDBUS if (SDL_SystemTheme_Init()) diff --git a/src/video/wayland/SDL_waylandvulkan.c b/src/video/wayland/SDL_waylandvulkan.c index 30f7da68e7..448a8d6e1d 100644 --- a/src/video/wayland/SDL_waylandvulkan.c +++ b/src/video/wayland/SDL_waylandvulkan.c @@ -41,6 +41,13 @@ #define DEFAULT_VULKAN "libvulkan.so.1" #endif +SDL_ELF_NOTE_DLOPEN( + "wayland-vulkan", + "Support for Vulkan on wayland backend", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + DEFAULT_VULKAN +); + bool Wayland_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) { VkExtensionProperties *extensions = NULL; diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index a8dd4251f8..54f3c71201 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -2543,6 +2543,49 @@ bool Wayland_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, b return true; } +bool Wayland_ReconfigureWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_WindowFlags flags) +{ + SDL_WindowData *data = window->internal; + + if (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { + // Window is already mapped; abort. + return false; + } + + /* The caller guarantees that only one of the GL or Vulkan flags will be set, + * and the window will have no previous video flags. + */ + if (flags & SDL_WINDOW_OPENGL) { + if (!data->egl_window) { + data->egl_window = WAYLAND_wl_egl_window_create(data->surface, data->current.pixel_width, data->current.pixel_height); + } + +#ifdef SDL_VIDEO_OPENGL_EGL + // Create the GLES window surface + data->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)data->egl_window); + + if (data->egl_surface == EGL_NO_SURFACE) { + return false; // SDL_EGL_CreateSurface should have set error + } +#endif + + if (!data->gles_swap_frame_event_queue) { + data->gles_swap_frame_event_queue = WAYLAND_wl_display_create_queue(data->waylandData->display); + data->gles_swap_frame_surface_wrapper = WAYLAND_wl_proxy_create_wrapper(data->surface); + WAYLAND_wl_proxy_set_queue((struct wl_proxy *)data->gles_swap_frame_surface_wrapper, data->gles_swap_frame_event_queue); + data->gles_swap_frame_callback = wl_surface_frame(data->gles_swap_frame_surface_wrapper); + wl_callback_add_listener(data->gles_swap_frame_callback, &gles_swap_frame_listener, data); + } + + return true; + } else if (flags & SDL_WINDOW_VULKAN) { + // Nothing to configure for Vulkan. + return true; + } + + return false; +} + bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_WindowData *data; diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 945b753365..e16f2d9d1a 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -253,6 +253,7 @@ extern void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *win extern bool Wayland_SetWindowHitTest(SDL_Window *window, bool enabled); extern bool Wayland_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); extern bool Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern bool Wayland_ReconfigureWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_WindowFlags flags); extern void Wayland_RemoveOutputFromWindow(SDL_WindowData *window, SDL_DisplayData *display_data); diff --git a/src/video/windows/SDL_surface_utils.c b/src/video/windows/SDL_surface_utils.c deleted file mode 100644 index 3e80438104..0000000000 --- a/src/video/windows/SDL_surface_utils.c +++ /dev/null @@ -1,97 +0,0 @@ -/* - 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_internal.h" - -#include "SDL_surface_utils.h" - -#include "../SDL_surface_c.h" - -#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) -HICON CreateIconFromSurface(SDL_Surface *surface) -{ - SDL_Surface *s = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888); - if (!s) { - return NULL; - } - - /* The dimensions will be needed after s is freed */ - const int width = s->w; - const int height = s->h; - - BITMAPINFO bmpInfo; - SDL_zero(bmpInfo); - bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - bmpInfo.bmiHeader.biWidth = width; - bmpInfo.bmiHeader.biHeight = -height; /* Top-down bitmap */ - bmpInfo.bmiHeader.biPlanes = 1; - bmpInfo.bmiHeader.biBitCount = 32; - bmpInfo.bmiHeader.biCompression = BI_RGB; - - HDC hdc = GetDC(NULL); - void *pBits = NULL; - HBITMAP hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, &pBits, NULL, 0); - if (!hBitmap) { - ReleaseDC(NULL, hdc); - SDL_DestroySurface(s); - return NULL; - } - - SDL_memcpy(pBits, s->pixels, width * height * 4); - - SDL_DestroySurface(s); - - HBITMAP hMask = CreateBitmap(width, height, 1, 1, NULL); - if (!hMask) { - DeleteObject(hBitmap); - ReleaseDC(NULL, hdc); - return NULL; - } - - HDC hdcMem = CreateCompatibleDC(hdc); - HGDIOBJ oldBitmap = SelectObject(hdcMem, hMask); - - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - BYTE* pixel = (BYTE*)pBits + (y * width + x) * 4; - BYTE alpha = pixel[3]; - COLORREF maskColor = (alpha == 0) ? RGB(0, 0, 0) : RGB(255, 255, 255); - SetPixel(hdcMem, x, y, maskColor); - } - } - - ICONINFO iconInfo; - iconInfo.fIcon = TRUE; - iconInfo.xHotspot = 0; - iconInfo.yHotspot = 0; - iconInfo.hbmMask = hMask; - iconInfo.hbmColor = hBitmap; - - HICON hIcon = CreateIconIndirect(&iconInfo); - - SelectObject(hdcMem, oldBitmap); - DeleteDC(hdcMem); - DeleteObject(hBitmap); - DeleteObject(hMask); - ReleaseDC(NULL, hdc); - - return hIcon; -} -#endif diff --git a/src/video/windows/SDL_surface_utils.h b/src/video/windows/SDL_surface_utils.h deleted file mode 100644 index a793cdcf8c..0000000000 --- a/src/video/windows/SDL_surface_utils.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - 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_internal.h" - -#ifndef SDL_surface_utils_h_ -#define SDL_surface_utils_h_ - -#include "../../core/windows/SDL_windows.h" - -#ifdef __cplusplus -extern "C" { -#endif - -extern HICON CreateIconFromSurface(SDL_Surface *surface); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/video/windows/SDL_windowsframebuffer.c b/src/video/windows/SDL_windowsframebuffer.c index f2bbd590a9..d9af714bfc 100644 --- a/src/video/windows/SDL_windowsframebuffer.c +++ b/src/video/windows/SDL_windowsframebuffer.c @@ -59,6 +59,9 @@ bool WIN_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, SDL GetDIBits(data->hdc, hbm, 0, 0, NULL, info, DIB_RGB_COLORS); DeleteObject(hbm); + // Check if a transparent channel is required + bool need_alpha = (window->flags & SDL_WINDOW_TRANSPARENT) != 0; + *format = SDL_PIXELFORMAT_UNKNOWN; if (info->bmiHeader.biCompression == BI_BITFIELDS) { int bpp; @@ -68,16 +71,22 @@ bool WIN_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, SDL masks = (Uint32 *)((Uint8 *)info + info->bmiHeader.biSize); *format = SDL_GetPixelFormatForMasks(bpp, masks[0], masks[1], masks[2], 0); } - if (*format == SDL_PIXELFORMAT_UNKNOWN) { - // We'll use RGB format for now - *format = SDL_PIXELFORMAT_XRGB8888; + if (*format == SDL_PIXELFORMAT_UNKNOWN || need_alpha) { + // We'll use RGB or BGRA32 format for now + *format = need_alpha ? SDL_PIXELFORMAT_BGRA32 : SDL_PIXELFORMAT_XRGB8888; // Create a new one SDL_memset(info, 0, size); info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); info->bmiHeader.biPlanes = 1; info->bmiHeader.biBitCount = 32; - info->bmiHeader.biCompression = BI_RGB; + info->bmiHeader.biCompression = need_alpha ? BI_BITFIELDS : BI_RGB; + + if (need_alpha) { + int tmpbpp; + Uint32 *bgr32masks = (Uint32 *)((Uint8 *)info + info->bmiHeader.biSize); + SDL_GetMasksForPixelFormat(SDL_PIXELFORMAT_BGRA32, &tmpbpp, &bgr32masks[0], &bgr32masks[1], &bgr32masks[2], &bgr32masks[3]); + } } // Fill in the size information diff --git a/src/video/windows/SDL_windowskeyboard.c b/src/video/windows/SDL_windowskeyboard.c index c8342c3cef..081bcaebe2 100644 --- a/src/video/windows/SDL_windowskeyboard.c +++ b/src/video/windows/SDL_windowskeyboard.c @@ -240,7 +240,6 @@ void WIN_QuitKeyboard(SDL_VideoDevice *_this) } #endif // !SDL_DISABLE_WINDOWS_IME - SDL_SetKeymap(NULL, false); for (int i = 0; i < keymap_cache_size; ++i) { SDL_DestroyKeymap(keymap_cache[i].keymap); } diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index aadfbd734b..259ba3c239 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -43,44 +43,6 @@ #include #endif -// Dark mode support -typedef enum { - UXTHEME_APPMODE_DEFAULT, - UXTHEME_APPMODE_ALLOW_DARK, - UXTHEME_APPMODE_FORCE_DARK, - UXTHEME_APPMODE_FORCE_LIGHT, - UXTHEME_APPMODE_MAX -} UxthemePreferredAppMode; - -typedef enum { - WCA_UNDEFINED = 0, - WCA_USEDARKMODECOLORS = 26, - WCA_LAST = 27 -} WINDOWCOMPOSITIONATTRIB; - -typedef struct { - WINDOWCOMPOSITIONATTRIB Attrib; - PVOID pvData; - SIZE_T cbData; -} WINDOWCOMPOSITIONATTRIBDATA; - -typedef struct { - ULONG dwOSVersionInfoSize; - ULONG dwMajorVersion; - ULONG dwMinorVersion; - ULONG dwBuildNumber; - ULONG dwPlatformId; - WCHAR szCSDVersion[128]; -} NT_OSVERSIONINFOW; - -typedef bool (WINAPI *ShouldAppsUseDarkMode_t)(void); -typedef void (WINAPI *AllowDarkModeForWindow_t)(HWND, bool); -typedef void (WINAPI *AllowDarkModeForApp_t)(bool); -typedef void (WINAPI *RefreshImmersiveColorPolicyState_t)(void); -typedef UxthemePreferredAppMode (WINAPI *SetPreferredAppMode_t)(UxthemePreferredAppMode); -typedef BOOL (WINAPI *SetWindowCompositionAttribute_t)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *); -typedef void (NTAPI *RtlGetVersion_t)(NT_OSVERSIONINFOW *); - // Windows CE compatibility #ifndef SWP_NOCOPYBITS #define SWP_NOCOPYBITS 0 @@ -775,8 +737,10 @@ bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properties return false; } +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) // Ensure that the IME isn't active on the new window until explicitly requested. WIN_StopTextInput(_this, window); +#endif // Inform Windows of the frame change so we can respond to WM_NCCALCSIZE SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE); @@ -2298,74 +2262,6 @@ bool WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool foc } #endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) -void WIN_UpdateDarkModeForHWND(HWND hwnd) -{ -#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) - HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll")); - if (!ntdll) { - return; - } - // There is no function to get Windows build number, so let's get it here via RtlGetVersion - RtlGetVersion_t RtlGetVersionFunc = (RtlGetVersion_t)GetProcAddress(ntdll, "RtlGetVersion"); - NT_OSVERSIONINFOW os_info; - os_info.dwOSVersionInfoSize = sizeof(NT_OSVERSIONINFOW); - os_info.dwBuildNumber = 0; - if (RtlGetVersionFunc) { - RtlGetVersionFunc(&os_info); - } - FreeLibrary(ntdll); - os_info.dwBuildNumber &= ~0xF0000000; - if (os_info.dwBuildNumber < 17763) { - // Too old to support dark mode - return; - } - HMODULE uxtheme = LoadLibrary(TEXT("uxtheme.dll")); - if (!uxtheme) { - return; - } - RefreshImmersiveColorPolicyState_t RefreshImmersiveColorPolicyStateFunc = (RefreshImmersiveColorPolicyState_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(104)); - ShouldAppsUseDarkMode_t ShouldAppsUseDarkModeFunc = (ShouldAppsUseDarkMode_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(132)); - AllowDarkModeForWindow_t AllowDarkModeForWindowFunc = (AllowDarkModeForWindow_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(133)); - if (os_info.dwBuildNumber < 18362) { - AllowDarkModeForApp_t AllowDarkModeForAppFunc = (AllowDarkModeForApp_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135)); - if (AllowDarkModeForAppFunc) { - AllowDarkModeForAppFunc(true); - } - } else { - SetPreferredAppMode_t SetPreferredAppModeFunc = (SetPreferredAppMode_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135)); - if (SetPreferredAppModeFunc) { - SetPreferredAppModeFunc(UXTHEME_APPMODE_ALLOW_DARK); - } - } - if (RefreshImmersiveColorPolicyStateFunc) { - RefreshImmersiveColorPolicyStateFunc(); - } - if (AllowDarkModeForWindowFunc) { - AllowDarkModeForWindowFunc(hwnd, true); - } - BOOL value; - // Check dark mode using ShouldAppsUseDarkMode, but use SDL_GetSystemTheme as a fallback - if (ShouldAppsUseDarkModeFunc) { - value = ShouldAppsUseDarkModeFunc() ? TRUE : FALSE; - } else { - value = (SDL_GetSystemTheme() == SDL_SYSTEM_THEME_DARK) ? TRUE : FALSE; - } - FreeLibrary(uxtheme); - if (os_info.dwBuildNumber < 18362) { - SetProp(hwnd, TEXT("UseImmersiveDarkModeColors"), SDL_reinterpret_cast(HANDLE, SDL_static_cast(INT_PTR, value))); - } else { - HMODULE user32 = GetModuleHandle(TEXT("user32.dll")); - if (user32) { - SetWindowCompositionAttribute_t SetWindowCompositionAttributeFunc = (SetWindowCompositionAttribute_t)GetProcAddress(user32, "SetWindowCompositionAttribute"); - if (SetWindowCompositionAttributeFunc) { - WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &value, sizeof(value) }; - SetWindowCompositionAttributeFunc(hwnd, &data); - } - } - } -#endif -} - bool WIN_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent) { #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h index 8401ab65a3..9ee8d748a5 100644 --- a/src/video/windows/SDL_windowswindow.h +++ b/src/video/windows/SDL_windowswindow.h @@ -136,7 +136,6 @@ extern bool WIN_SetWindowHitTest(SDL_Window *window, bool enabled); extern void WIN_AcceptDragAndDrop(SDL_Window *window, bool accept); extern bool WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); extern bool WIN_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window *window); -extern void WIN_UpdateDarkModeForHWND(HWND hwnd); extern bool WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags, SDL_WindowRect rect_type); extern void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y); extern bool WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable); diff --git a/src/video/x11/SDL_x11dyn.c b/src/video/x11/SDL_x11dyn.c index 116f5655e8..efab3f72b4 100644 --- a/src/video/x11/SDL_x11dyn.c +++ b/src/video/x11/SDL_x11dyn.c @@ -38,25 +38,87 @@ typedef struct const char *libname; } x11dynlib; -#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT +SDL_ELF_NOTE_DLOPEN( + "x11", + "Support for video through X11 backend", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_VIDEO_DRIVER_X11_DYNAMIC +); + +#ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT +SDL_ELF_NOTE_DLOPEN( + "x11", + "Support for video through X11 backend", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT +); +#else #define SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT NULL #endif -#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR + +#ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR +SDL_ELF_NOTE_DLOPEN( + "x11", + "Support for video through X11 backend", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR +); +#else #define SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR NULL #endif -#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 + +#ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 +SDL_ELF_NOTE_DLOPEN( + "x11", + "Support for video through X11 backend", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 +); +#else #define SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 NULL #endif -#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES + +#ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES +SDL_ELF_NOTE_DLOPEN( + "x11", + "Support for video through X11 backend", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES +); +#else #define SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES NULL #endif -#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR + +#ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR +SDL_ELF_NOTE_DLOPEN( + "x11", + "Support for video through X11 backend", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR +); +#else #define SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR NULL #endif -#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS + +#ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS +SDL_ELF_NOTE_DLOPEN( + "x11", + "Support for video through X11 backend", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS +); +#else #define SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS NULL #endif -#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST + +#ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST +SDL_ELF_NOTE_DLOPEN( + "x11", + "Support for video through X11 backend", + SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST +); +#else #define SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST NULL #endif diff --git a/src/video/x11/SDL_x11keyboard.c b/src/video/x11/SDL_x11keyboard.c index d26087a12f..45532e3ba7 100644 --- a/src/video/x11/SDL_x11keyboard.c +++ b/src/video/x11/SDL_x11keyboard.c @@ -380,7 +380,6 @@ void X11_UpdateKeymap(SDL_VideoDevice *_this, bool send_event) if (data->keyboard.xkb_enabled) { XkbStateRec state; - SDL_SetKeymap(NULL, false); for (unsigned int i = 0; i < XkbNumKbdGroups; ++i) { SDL_DestroyKeymap(data->keyboard.xkb.keymaps[i]); data->keyboard.xkb.keymaps[i] = SDL_CreateKeymap(false); @@ -515,7 +514,6 @@ void X11_QuitKeyboard(SDL_VideoDevice *_this) #ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB if (data->keyboard.xkb_enabled) { - SDL_SetKeymap(NULL, false); for (int i = 0; i < XkbNumKbdGroups; ++i) { SDL_DestroyKeymap(data->keyboard.xkb.keymaps[i]); data->keyboard.xkb.keymaps[i] = NULL; diff --git a/src/video/x11/SDL_x11messagebox.c b/src/video/x11/SDL_x11messagebox.c index dd4a1bf691..05a3f84418 100644 --- a/src/video/x11/SDL_x11messagebox.c +++ b/src/video/x11/SDL_x11messagebox.c @@ -23,22 +23,13 @@ #ifdef SDL_VIDEO_DRIVER_X11 -#include "SDL_x11video.h" -#include "SDL_x11dyn.h" #include "SDL_x11messagebox.h" - -#include -#include +#include "SDL_x11toolkit.h" #ifndef SDL_FORK_MESSAGEBOX #define SDL_FORK_MESSAGEBOX 1 #endif -#define SDL_SET_LOCALE 1 -#define SDL_DIALOG_ELEMENT_PADDING 4 -#define SDL_DIALOG_ELEMENT_PADDING_2 12 -#define SDL_DIALOG_ELEMENT_PADDING_3 8 - #if SDL_FORK_MESSAGEBOX #include #include @@ -46,1183 +37,194 @@ #include #endif -#define MAX_BUTTONS 8 // Maximum number of buttons supported -#define MIN_BUTTON_WIDTH 32 // Minimum button width -#define MIN_DIALOG_WIDTH 10 // Minimum dialog width -#define MIN_DIALOG_HEIGHT 10 // Minimum dialog height - -static const char g_MessageBoxFontLatin1[] = - "-*-*-medium-r-normal--0-120-*-*-p-0-iso8859-1"; - -static const char *g_MessageBoxFont[] = { - "-*-*-medium-r-normal--*-120-*-*-*-*-iso10646-1", // explicitly unicode (iso10646-1) - "-*-*-medium-r-*--*-120-*-*-*-*-iso10646-1", // explicitly unicode (iso10646-1) - "-misc-*-*-*-*--*-*-*-*-*-*-iso10646-1", // misc unicode (fix for some systems) - "-*-*-*-*-*--*-*-*-*-*-*-iso10646-1", // just give me anything Unicode. - "-*-*-medium-r-normal--*-120-*-*-*-*-iso8859-1", // explicitly latin1, in case low-ASCII works out. - "-*-*-medium-r-*--*-120-*-*-*-*-iso8859-1", // explicitly latin1, in case low-ASCII works out. - "-misc-*-*-*-*--*-*-*-*-*-*-iso8859-1", // misc latin1 (fix for some systems) - "-*-*-*-*-*--*-*-*-*-*-*-iso8859-1", // just give me anything latin1. - NULL -}; - -static const char *g_IconFont = "-*-*-bold-r-normal-*-18-*-*-*-*-*-iso8859-1[33 88 105]"; - -static const SDL_MessageBoxColor g_default_colors[SDL_MESSAGEBOX_COLOR_COUNT] = { - { 191, 184, 191 }, // SDL_MESSAGEBOX_COLOR_BACKGROUND, - { 0, 0, 0 }, // SDL_MESSAGEBOX_COLOR_TEXT, - { 127, 120, 127 }, // SDL_MESSAGEBOX_COLOR_BUTTON_BORDER, - { 191, 184, 191 }, // SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND, - { 235, 235, 235 }, // SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED, -}; - -typedef struct SDL_MessageBoxButtonDataX11 +typedef struct SDL_MessageBoxCallbackDataX11 { - int length; // Text length - int text_a; - int text_d; - - SDL_Rect text_rect; - SDL_Rect rect; // Rectangle for entire button + int *buttonID; + SDL_ToolkitWindowX11 *window; +} SDL_MessageBoxCallbackDataX11; - const SDL_MessageBoxButtonData *buttondata; // Button data from caller -} SDL_MessageBoxButtonDataX11; - -typedef struct TextLineData +typedef struct SDL_MessageBoxControlsX11 { - int length; // String length of this text line - const char *text; // Text for this line - SDL_Rect rect; -} TextLineData; - -typedef struct SDL_MessageBoxDataX11 -{ - Display *display; - int screen; - Window window; - Visual *visual; - Colormap cmap; -#ifdef SDL_VIDEO_DRIVER_X11_XDBE - XdbeBackBuffer buf; - bool xdbe; // Whether Xdbe is present or not -#endif - long event_mask; - Atom wm_protocols; - Atom wm_delete_message; -#ifdef SDL_VIDEO_DRIVER_X11_XRANDR - bool xrandr; // Whether Xrandr is present or not -#endif - - int dialog_width; // Dialog box width. - int dialog_height; // Dialog box height. - - XFontSet font_set; // for UTF-8 systems - XFontStruct *font_struct; // Latin1 (ASCII) fallback. - int numlines; // Count of Text lines. - int text_height; // Height for text lines. - TextLineData *linedata; - - char icon_char; // Icon, '\0' indicates that the messsage box has no icon. - XFontStruct *icon_char_font; - SDL_Rect icon_box_rect; - int icon_char_x; - int icon_char_y; - - int *pbuttonid; // Pointer to user return buttonID value. - - int button_press_index; // Index into buttondata/buttonpos for button which is pressed (or -1). - int mouse_over_index; // Index into buttondata/buttonpos for button mouse is over (or -1). - - int numbuttons; // Count of buttons. - const SDL_MessageBoxButtonData *buttondata; - SDL_MessageBoxButtonDataX11 buttonpos[MAX_BUTTONS]; - - /* Colors for rendering widgets */ - XColor xcolor[SDL_MESSAGEBOX_COLOR_COUNT]; - XColor xcolor_bevel_l1; - XColor xcolor_bevel_l2; - XColor xcolor_bevel_d; - XColor xcolor_pressed; - - /* Colors for rendering icons */ - XColor xcolor_black; - XColor xcolor_red; - XColor xcolor_red_darker; - XColor xcolor_white; - XColor xcolor_yellow; - XColor xcolor_blue; - XColor xcolor_bg_shadow; - + SDL_ToolkitWindowX11 *window; + SDL_ToolkitControlX11 *icon; + SDL_ToolkitControlX11 fake_icon; + SDL_ToolkitControlX11 *message; + SDL_ToolkitControlX11 **buttons; const SDL_MessageBoxData *messageboxdata; -} SDL_MessageBoxDataX11; +} SDL_MessageBoxControlsX11; -// Int helpers -static SDL_INLINE int IntMax(int a, int b) +static void X11_MessageBoxButtonCallback(SDL_ToolkitControlX11 *control, void *data) { - return (a > b) ? a : b; + SDL_MessageBoxCallbackDataX11 *cbdata; + + cbdata = (SDL_MessageBoxCallbackDataX11 *)data; + *cbdata->buttonID = X11Toolkit_GetButtonControlData(control)->buttonID; + X11Toolkit_SignalWindowClose(cbdata->window); } -static SDL_INLINE int IntClamp(int a, int b, int c) -{ - if (a < b) return b; - if (a > c) return c; - return a; -} - -static void GetTextWidthHeightForFont(SDL_MessageBoxDataX11 *data, XFontStruct *font, const char *str, int nbytes, int *pwidth, int *pheight, int *font_ascent) -{ - XCharStruct text_structure; - int font_direction, font_descent; - X11_XTextExtents(font, str, nbytes, - &font_direction, font_ascent, &font_descent, - &text_structure); - *pwidth = text_structure.width; - *pheight = text_structure.ascent + text_structure.descent; -} - -// Return width and height for a string. -static void GetTextWidthHeight(SDL_MessageBoxDataX11 *data, const char *str, int nbytes, int *pwidth, int *pheight, int *font_ascent, int *font_descent) -{ -#ifdef X_HAVE_UTF8_STRING - if (SDL_X11_HAVE_UTF8) { - XFontSetExtents *extents; - XRectangle overall_ink, overall_logical; - extents = X11_XExtentsOfFontSet(data->font_set); - X11_Xutf8TextExtents(data->font_set, str, nbytes, &overall_ink, &overall_logical); - *pwidth = overall_logical.width; - *pheight = overall_logical.height; - *font_ascent = -extents->max_logical_extent.y; - *font_descent = extents->max_logical_extent.height - *font_ascent; - } else -#endif - { - XCharStruct text_structure; - int font_direction; - X11_XTextExtents(data->font_struct, str, nbytes, - &font_direction, font_ascent, font_descent, - &text_structure); - *pwidth = text_structure.width; - *pheight = text_structure.ascent + text_structure.descent; - } -} - -// Return index of button if position x,y is contained therein. -static int GetHitButtonIndex(SDL_MessageBoxDataX11 *data, int x, int y) -{ - int i; - int numbuttons = data->numbuttons; - SDL_MessageBoxButtonDataX11 *buttonpos = data->buttonpos; - - for (i = 0; i < numbuttons; i++) { - SDL_Rect *rect = &buttonpos[i].rect; - - if ((x >= rect->x) && - (x <= (rect->x + rect->w)) && - (y >= rect->y) && - (y <= (rect->y + rect->h))) { - return i; - } - } - - return -1; -} - -// Initialize SDL_MessageBoxData structure and Display, etc. -static bool X11_MessageBoxInit(SDL_MessageBoxDataX11 *data, const SDL_MessageBoxData *messageboxdata, int *pbuttonid) -{ - int i; - int numbuttons = messageboxdata->numbuttons; - const SDL_MessageBoxButtonData *buttondata = messageboxdata->buttons; - const SDL_MessageBoxColor *colorhints; - - if (numbuttons > MAX_BUTTONS) { - return SDL_SetError("Too many buttons (%d max allowed)", MAX_BUTTONS); - } - - data->dialog_width = MIN_DIALOG_WIDTH; - data->dialog_height = MIN_DIALOG_HEIGHT; - data->messageboxdata = messageboxdata; - data->buttondata = buttondata; - data->numbuttons = numbuttons; - data->pbuttonid = pbuttonid; - - // Convert flags to icon character - switch (data->messageboxdata->flags & (SDL_MESSAGEBOX_ERROR | SDL_MESSAGEBOX_WARNING | SDL_MESSAGEBOX_INFORMATION)) { - case SDL_MESSAGEBOX_ERROR: - data->icon_char = 'X'; - break; - case SDL_MESSAGEBOX_WARNING: - data->icon_char = '!'; - break; - case SDL_MESSAGEBOX_INFORMATION: - data->icon_char = 'i'; - break; - default: - data->icon_char = '\0'; - } - - data->display = X11_XOpenDisplay(NULL); - if (!data->display) { - return SDL_SetError("Couldn't open X11 display"); - } - -#ifdef SDL_VIDEO_DRIVER_X11_XRANDR - int xrandr_event_base, xrandr_error_base; - data->xrandr = X11_XRRQueryExtension(data->display, &xrandr_event_base, &xrandr_error_base); -#endif - -#ifdef X_HAVE_UTF8_STRING - if (SDL_X11_HAVE_UTF8) { - char **missing = NULL; - int num_missing = 0; - int i_font; - for (i_font = 0; g_MessageBoxFont[i_font]; ++i_font) { - data->font_set = X11_XCreateFontSet(data->display, g_MessageBoxFont[i_font], - &missing, &num_missing, NULL); - if (missing) { - X11_XFreeStringList(missing); - } - if (data->font_set) { - break; - } - } - if (!data->font_set) { - return SDL_SetError("Couldn't load x11 message box font"); - } - } else -#endif - { - data->font_struct = X11_XLoadQueryFont(data->display, g_MessageBoxFontLatin1); - if (!data->font_struct) { - return SDL_SetError("Couldn't load font %s", g_MessageBoxFontLatin1); - } - } - - if (data->icon_char != '\0') { - data->icon_char_font = X11_XLoadQueryFont(data->display, g_IconFont); - if (!data->icon_char_font) { - data->icon_char_font = X11_XLoadQueryFont(data->display, g_MessageBoxFontLatin1); - if (!data->icon_char_font) { - data->icon_char = '\0'; - } - } - } - - if (messageboxdata->colorScheme) { - colorhints = messageboxdata->colorScheme->colors; - } else { - colorhints = g_default_colors; - } - - // Convert colors to 16 bpc XColor format - for (i = 0; i < SDL_MESSAGEBOX_COLOR_COUNT; i++) { - data->xcolor[i].flags = DoRed|DoGreen|DoBlue; - data->xcolor[i].red = colorhints[i].r * 257; - data->xcolor[i].green = colorhints[i].g * 257; - data->xcolor[i].blue = colorhints[i].b * 257; - } - - /* Generate bevel and pressed colors */ - data->xcolor_bevel_l1.flags = DoRed|DoGreen|DoBlue; - data->xcolor_bevel_l1.red = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].red + 12500, 0, 65535); - data->xcolor_bevel_l1.green = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].green + 12500, 0, 65535); - data->xcolor_bevel_l1.blue = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].blue + 12500, 0, 65535); - - data->xcolor_bevel_l2.flags = DoRed|DoGreen|DoBlue; - data->xcolor_bevel_l2.red = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].red + 32500, 0, 65535); - data->xcolor_bevel_l2.green = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].green + 32500, 0, 65535); - data->xcolor_bevel_l2.blue = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].blue + 32500, 0, 65535); - - data->xcolor_bevel_d.flags = DoRed|DoGreen|DoBlue; - data->xcolor_bevel_d.red = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].red - 22500, 0, 65535); - data->xcolor_bevel_d.green = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].green - 22500, 0, 65535); - data->xcolor_bevel_d.blue = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].blue - 22500, 0, 65535); - - data->xcolor_pressed.flags = DoRed|DoGreen|DoBlue; - data->xcolor_pressed.red = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND].red - 12500, 0, 65535); - data->xcolor_pressed.green = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND].green - 12500, 0, 65535); - data->xcolor_pressed.blue = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND].blue - 12500, 0, 65535); - - /* Icon colors */ - if (data->icon_char != '\0') { - data->xcolor_black.flags = DoRed|DoGreen|DoBlue; - data->xcolor_black.red = 0; - data->xcolor_black.green = 0; - data->xcolor_black.blue = 0; - - data->xcolor_black.flags = DoRed|DoGreen|DoBlue; - data->xcolor_white.red = 65535; - data->xcolor_white.green = 65535; - data->xcolor_white.blue = 65535; - - data->xcolor_red.flags = DoRed|DoGreen|DoBlue; - data->xcolor_red.red = 65535; - data->xcolor_red.green = 0; - data->xcolor_red.blue = 0; - - data->xcolor_red_darker.flags = DoRed|DoGreen|DoBlue; - data->xcolor_red_darker.red = 40535; - data->xcolor_red_darker.green = 0; - data->xcolor_red_darker.blue = 0; - - data->xcolor_yellow.flags = DoRed|DoGreen|DoBlue; - data->xcolor_yellow.red = 65535; - data->xcolor_yellow.green = 65535; - data->xcolor_yellow.blue = 0; - - data->xcolor_blue.flags = DoRed|DoGreen|DoBlue; - data->xcolor_blue.red = 0; - data->xcolor_blue.green = 0; - data->xcolor_blue.blue = 65535; - - data->xcolor_bg_shadow.flags = DoRed|DoGreen|DoBlue; - data->xcolor_bg_shadow.red = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].red - 12500, 0, 65535); - data->xcolor_bg_shadow.green = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].green - 12500, 0, 65535); - data->xcolor_bg_shadow.blue = IntClamp(data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].blue - 12500, 0, 65535); - } - - return true; -} - -static int CountLinesOfText(const char *text) -{ - int result = 0; - while (text && *text) { - const char *lf = SDL_strchr(text, '\n'); - result++; // even without an endline, this counts as a line. - text = lf ? lf + 1 : NULL; - } - return result; -} - -// Calculate and initialize text and button locations. -static bool X11_MessageBoxInitPositions(SDL_MessageBoxDataX11 *data) -{ - int paddingx2; - int padding2x2; - int text_width_max; - int text_height_total; - int button_height_max; - int button_width_max; - int button_width_total; - int elems_total_height; - int text_ix; +static void X11_PositionMessageBox(SDL_MessageBoxControlsX11 *controls, int *wp, int *hp) { + int max_button_w; + int max_button_h; + int total_button_w; + int total_text_and_icon_w; + int w; + int h; int i; int t; - const SDL_MessageBoxData *messageboxdata = data->messageboxdata; - /* Optimization and initialization */ - text_height_total = 0; - text_width_max = 0; - paddingx2 = SDL_DIALOG_ELEMENT_PADDING * 2; - padding2x2 = SDL_DIALOG_ELEMENT_PADDING_2 * 2; - data->icon_char_y = 0; - data->icon_box_rect.w = 0; - data->icon_box_rect.h = 0; - text_ix = 0; - t = 0; - button_width_max = MIN_BUTTON_WIDTH; - button_height_max = 0; - - /* Calculate icon sizing */ - if (data->icon_char != '\0') { - int icon_char_w; - int icon_char_h; - int icon_char_a; - int icon_char_max; - - GetTextWidthHeightForFont(data, data->icon_char_font, &data->icon_char, 1, &icon_char_w, &icon_char_h, &icon_char_a); - data->icon_box_rect.w = icon_char_w + paddingx2; - data->icon_box_rect.h = icon_char_h + paddingx2; - icon_char_max = IntMax(data->icon_box_rect.w, data->icon_box_rect.h) + 2; - data->icon_box_rect.w = icon_char_max; - data->icon_box_rect.h = icon_char_max; - data->icon_box_rect.y = 0; - data->icon_box_rect.x = 0; - data->icon_char_y = icon_char_a + data->icon_box_rect.y + (data->icon_box_rect.h - icon_char_h)/2 + 1; - data->icon_char_x = data->icon_box_rect.x + (data->icon_box_rect.w - icon_char_w)/2 + 1; - data->icon_box_rect.w += 2; - data->icon_box_rect.h += 2; - } - - // Go over text and break linefeeds into separate lines. - if (messageboxdata && messageboxdata->message[0]) { - int iascent; - const char *text = messageboxdata->message; - const int linecount = CountLinesOfText(text); - TextLineData *plinedata = (TextLineData *)SDL_malloc(sizeof(TextLineData) * linecount); + /* Init vars */ + max_button_w = 50; + max_button_h = 0; + w = h = 2; + i = t = total_button_w = total_text_and_icon_w = 0; + max_button_w *= controls->window->iscale; - if (!plinedata) { - return false; - } - - data->linedata = plinedata; - data->numlines = linecount; - iascent = 0; - - for (i = 0; i < linecount; i++) { - const char *lf = SDL_strchr(text, '\n'); - const int length = lf ? (lf - text) : SDL_strlen(text); - int ascent; - int descent; - - plinedata[i].text = text; - - GetTextWidthHeight(data, text, length, &plinedata[i].rect.w, &plinedata[i].rect.h, &ascent, &descent); - - // Text widths are the largest we've ever seen. - text_width_max = IntMax(text_width_max, plinedata[i].rect.w); - - plinedata[i].length = length; - if (lf && (lf > text) && (lf[-1] == '\r')) { - plinedata[i].length--; - } - - if (i > 0) { - plinedata[i].rect.y = ascent + descent + plinedata[i-1].rect.y; - } else { - plinedata[i].rect.y = data->icon_box_rect.y + SDL_DIALOG_ELEMENT_PADDING + ascent; - iascent = ascent; - } - plinedata[i].rect.x = text_ix = data->icon_box_rect.x + data->icon_box_rect.w + SDL_DIALOG_ELEMENT_PADDING_2; - text += length + 1; - - // Break if there are no more linefeeds. - if (!lf) { - break; - } - } - - text_height_total = plinedata[linecount-1].rect.y + plinedata[linecount-1].rect.h - iascent - data->icon_box_rect.y - SDL_DIALOG_ELEMENT_PADDING; + /* Positioning and sizing */ + for (i = 0; i < controls->messageboxdata->numbuttons; i++) { + max_button_w = SDL_max(max_button_w, controls->buttons[i]->rect.w); + max_button_h = SDL_max(max_button_h, controls->buttons[i]->rect.h); + controls->buttons[i]->rect.x = 0; } - - // Loop through all buttons and calculate the button widths and height. - for (i = 0; i < data->numbuttons; i++) { - data->buttonpos[i].buttondata = &data->buttondata[i]; - data->buttonpos[i].length = SDL_strlen(data->buttondata[i].text); - GetTextWidthHeight(data, data->buttondata[i].text, SDL_strlen(data->buttondata[i].text), &data->buttonpos[i].text_rect.w, &data->buttonpos[i].text_rect.h, &data->buttonpos[i].text_a, &data->buttonpos[i].text_d); + if (controls->icon) { + controls->icon->rect.x = controls->icon->rect.y = 0; + } - button_height_max = IntMax(button_height_max, (data->buttonpos[i].text_rect.h + SDL_DIALOG_ELEMENT_PADDING_3 * 2)); - button_width_max = IntMax(button_width_max, (data->buttonpos[i].text_rect.w + padding2x2)); - - } - button_width_total = button_width_max * data->numbuttons + SDL_DIALOG_ELEMENT_PADDING * (data->numbuttons - 1); - button_width_total += padding2x2; - - /* Dialog width */ - if (button_width_total < (text_ix + text_width_max + padding2x2)) { - data->dialog_width = IntMax(data->dialog_width, (text_ix + text_width_max + padding2x2)); + if (controls->icon) { + controls->message->rect.x = (SDL_TOOLKIT_X11_ELEMENT_PADDING_2 * controls->window->iscale) + controls->icon->rect.x + controls->icon->rect.w; + controls->message->rect.y = X11Toolkit_GetIconControlCharY(controls->icon); } else { - data->dialog_width = IntMax(data->dialog_width, button_width_total); + controls->message->rect.x = 0; + controls->message->rect.y = -2; + controls->icon = &controls->fake_icon; + controls->icon->rect.w = 0; + controls->icon->rect.h = 0; + controls->icon->rect.x = 0; + controls->icon->rect.y = 0; } - - /* X position of text and icon */ - t = (data->dialog_width - (text_ix + text_width_max))/2; - if (data->icon_char != '\0') { - data->icon_box_rect.y = 0; - if (data->numlines) { - data->icon_box_rect.x += t; - data->icon_char_x += t; - } else { - int tt; - - tt = t; - t = (data->dialog_width - data->icon_box_rect.w)/2; - data->icon_box_rect.x += t; - data->icon_char_x += t; - t = tt; - } - } - for (i = 0; i < data->numlines; i++) { - data->linedata[i].rect.x += t; - } - - /* Button poistioning */ - for (i = 0; i < data->numbuttons; i++) { - data->buttonpos[i].rect.w = button_width_max; - data->buttonpos[i].text_rect.x = (data->buttonpos[i].rect.w - data->buttonpos[i].text_rect.w)/2; - data->buttonpos[i].rect.h = button_height_max; - data->buttonpos[i].text_rect.y = data->buttonpos[i].text_a + (data->buttonpos[i].rect.h - data->buttonpos[i].text_rect.h)/2; - if (i > 0) { - data->buttonpos[i].rect.x += data->buttonpos[i-1].rect.x + data->buttonpos[i-1].rect.w + SDL_DIALOG_ELEMENT_PADDING_3; - data->buttonpos[i].text_rect.x += data->buttonpos[i].rect.x; - } - } - button_width_total = data->buttonpos[data->numbuttons-1].rect.x + data->buttonpos[data->numbuttons-1].rect.w; - data->dialog_width = IntMax(data->dialog_width, (button_width_total + padding2x2)); - if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT) { - for (i = 0; i < data->numbuttons; i++) { - data->buttonpos[i].rect.x += (data->dialog_width - button_width_total)/2; - data->buttonpos[i].text_rect.x += (data->dialog_width - button_width_total)/2; - if (data->icon_box_rect.h > text_height_total) { - data->buttonpos[i].text_rect.y += data->icon_box_rect.h + SDL_DIALOG_ELEMENT_PADDING_2 - 2; - data->buttonpos[i].rect.y += data->icon_box_rect.h + SDL_DIALOG_ELEMENT_PADDING_2; + if (controls->messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT) { + for (i = controls->messageboxdata->numbuttons; i != -1; i--) { + controls->buttons[i]->rect.w = max_button_w; + controls->buttons[i]->rect.h = max_button_h; + X11Toolkit_NotifyControlOfSizeChange(controls->buttons[i]); + + if (controls->icon->rect.h > controls->message->rect.h) { + controls->buttons[i]->rect.y = controls->icon->rect.h + (SDL_TOOLKIT_X11_ELEMENT_PADDING_2 *controls-> window->iscale); } else { - int a; - - a = 0; - if (data->numlines) { - a = data->linedata[data->numlines - 1].rect.y + data->linedata[data->numlines - 1].rect.h; - } - data->buttonpos[i].text_rect.y += a + SDL_DIALOG_ELEMENT_PADDING_2 - 2; - data->buttonpos[i].rect.y += a + SDL_DIALOG_ELEMENT_PADDING_2; + controls->buttons[i]->rect.y = controls->message->rect.h + (SDL_TOOLKIT_X11_ELEMENT_PADDING_2 * controls->window->iscale); } - } + + if (i) { + controls->buttons[i]->rect.x = controls->buttons[i-1]->rect.x + controls->buttons[i-1]->rect.w + (SDL_TOOLKIT_X11_ELEMENT_PADDING_3 * controls->window->iscale); + } + } } else { - for (i = data->numbuttons; i != -1; i--) { - data->buttonpos[i].rect.x += (data->dialog_width - button_width_total)/2; - data->buttonpos[i].text_rect.x += (data->dialog_width - button_width_total)/2; - if (data->icon_box_rect.h > text_height_total) { - data->buttonpos[i].text_rect.y += data->icon_box_rect.h + SDL_DIALOG_ELEMENT_PADDING_2 - 2; - data->buttonpos[i].rect.y += data->icon_box_rect.h + SDL_DIALOG_ELEMENT_PADDING_2; + for (i = 0; i < controls->messageboxdata->numbuttons; i++) { + controls->buttons[i]->rect.w = max_button_w; + controls->buttons[i]->rect.h = max_button_h; + X11Toolkit_NotifyControlOfSizeChange(controls->buttons[i]); + + if (controls->icon->rect.h > controls->message->rect.h) { + controls->buttons[i]->rect.y = controls->icon->rect.h + (SDL_TOOLKIT_X11_ELEMENT_PADDING_2 * controls->window->iscale); } else { - int a; - - a = 0; - if (data->numlines) { - a = data->linedata[data->numlines - 1].rect.y + data->linedata[data->numlines - 1].rect.h; - } - data->buttonpos[i].text_rect.y += a + SDL_DIALOG_ELEMENT_PADDING_2 - 2; - data->buttonpos[i].rect.y += a + SDL_DIALOG_ELEMENT_PADDING_2; + controls->buttons[i]->rect.y = controls->message->rect.h + (SDL_TOOLKIT_X11_ELEMENT_PADDING_2 * controls->window->iscale); } - } - } - /* Dialog height */ - elems_total_height = data->buttonpos[data->numbuttons-1].rect.y + data->buttonpos[data->numbuttons-1].rect.h; - data->dialog_height = IntMax(data->dialog_height, (elems_total_height + padding2x2)); - t = (data->dialog_height - elems_total_height)/2; - if (data->icon_char != '\0') { - data->icon_box_rect.y += t; - data->icon_char_y += t; - data->icon_box_rect.w -= 2; - data->icon_box_rect.h -= 2; - } - for (i = 0; i < data->numbuttons; i++) { - data->buttonpos[i].text_rect.y += t; - data->buttonpos[i].rect.y += t; - } - for (i = 0; i < data->numlines; i++) { - data->linedata[i].rect.y += t; - } - return true; -} - -// Free SDL_MessageBoxData data. -static void X11_MessageBoxShutdown(SDL_MessageBoxDataX11 *data) -{ - if (data->font_set) { - X11_XFreeFontSet(data->display, data->font_set); - data->font_set = NULL; - } - - if (data->font_struct) { - X11_XFreeFont(data->display, data->font_struct); - data->font_struct = NULL; - } - - if (data->icon_char != '\0') { - X11_XFreeFont(data->display, data->icon_char_font); - data->icon_char_font = NULL; - } - -#ifdef SDL_VIDEO_DRIVER_X11_XDBE - if (SDL_X11_HAVE_XDBE && data->xdbe) { - X11_XdbeDeallocateBackBufferName(data->display, data->buf); - } -#endif - - if (data->display) { - if (data->window != None) { - X11_XWithdrawWindow(data->display, data->window, data->screen); - X11_XDestroyWindow(data->display, data->window); - data->window = None; + if (i) { + controls->buttons[i]->rect.x = controls->buttons[i-1]->rect.x + controls->buttons[i-1]->rect.w + (SDL_TOOLKIT_X11_ELEMENT_PADDING_3 * controls->window->iscale); + } } - - X11_XCloseDisplay(data->display); - data->display = NULL; } - - SDL_free(data->linedata); -} - -// Create and set up our X11 dialog box indow. -static bool X11_MessageBoxCreateWindow(SDL_MessageBoxDataX11 *data) -{ - int x, y, i; - XSizeHints *sizehints; - XSetWindowAttributes wnd_attr; - Atom _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_DIALOG; - 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) { - SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(messageboxdata->window); - windowdata = messageboxdata->window->internal; - data->screen = displaydata->screen; + total_button_w = controls->buttons[controls->messageboxdata->numbuttons-1]->rect.x + controls->buttons[controls->messageboxdata->numbuttons-1]->rect.w; + total_text_and_icon_w = controls->message->rect.x + controls->message->rect.w; + if (total_button_w > total_text_and_icon_w) { + w = total_button_w; } else { - data->screen = DefaultScreen(display); + w = total_text_and_icon_w; } - - data->visual = DefaultVisual(display, data->screen); - data->cmap = DefaultColormap(display, data->screen); - for (i = 0; i < SDL_MESSAGEBOX_COLOR_COUNT; i++) { - X11_XAllocColor(display, data->cmap, &data->xcolor[i]); - } - X11_XAllocColor(display, data->cmap, &data->xcolor_bevel_l1); - X11_XAllocColor(display, data->cmap, &data->xcolor_bevel_l2); - X11_XAllocColor(display, data->cmap, &data->xcolor_bevel_d); - X11_XAllocColor(display, data->cmap, &data->xcolor_pressed); - if (data->icon_char != '\0') { - X11_XAllocColor(display, data->cmap, &data->xcolor_black); - X11_XAllocColor(display, data->cmap, &data->xcolor_white); - X11_XAllocColor(display, data->cmap, &data->xcolor_red); - X11_XAllocColor(display, data->cmap, &data->xcolor_red_darker); - X11_XAllocColor(display, data->cmap, &data->xcolor_yellow); - X11_XAllocColor(display, data->cmap, &data->xcolor_blue); - X11_XAllocColor(display, data->cmap, &data->xcolor_bg_shadow); - } - - data->event_mask = ExposureMask | - ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | - StructureNotifyMask | FocusChangeMask | PointerMotionMask; - wnd_attr.event_mask = data->event_mask; - wnd_attr.colormap = data->cmap; - - data->window = X11_XCreateWindow( - display, RootWindow(display, data->screen), - 0, 0, - data->dialog_width, data->dialog_height, - 0, DefaultDepth(display, data->screen), InputOutput, data->visual, - CWEventMask | CWColormap, &wnd_attr); - if (data->window == None) { - return SDL_SetError("Couldn't create X window"); - } - - if (windowdata) { - Atom _NET_WM_STATE = X11_XInternAtom(display, "_NET_WM_STATE", False); - Atom stateatoms[16]; - size_t statecount = 0; - // Set some message-boxy window states when attached to a parent window... - // we skip the taskbar since this will pop to the front when the parent window is clicked in the taskbar, etc - stateatoms[statecount++] = X11_XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", False); - stateatoms[statecount++] = X11_XInternAtom(display, "_NET_WM_STATE_SKIP_PAGER", False); - stateatoms[statecount++] = X11_XInternAtom(display, "_NET_WM_STATE_FOCUSED", False); - stateatoms[statecount++] = X11_XInternAtom(display, "_NET_WM_STATE_MODAL", False); - SDL_assert(statecount <= SDL_arraysize(stateatoms)); - X11_XChangeProperty(display, data->window, _NET_WM_STATE, XA_ATOM, 32, - PropModeReplace, (unsigned char *)stateatoms, statecount); - - // http://tronche.com/gui/x/icccm/sec-4.html#WM_TRANSIENT_FOR - X11_XSetTransientForHint(display, data->window, windowdata->xwindow); - } - - SDL_X11_SetWindowTitle(display, data->window, (char *)messageboxdata->title); - - // Let the window manager know this is a dialog box - _NET_WM_WINDOW_TYPE = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); - _NET_WM_WINDOW_TYPE_DIALOG = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", False); - X11_XChangeProperty(display, data->window, _NET_WM_WINDOW_TYPE, XA_ATOM, 32, - PropModeReplace, - (unsigned char *)&_NET_WM_WINDOW_TYPE_DIALOG, 1); - - // Allow the window to be deleted by the window manager - data->wm_delete_message = X11_XInternAtom(display, "WM_DELETE_WINDOW", False); - X11_XSetWMProtocols(display, data->window, &data->wm_delete_message, 1); - - data->wm_protocols = X11_XInternAtom(display, "WM_PROTOCOLS", False); - - if (windowdata) { - XWindowAttributes attrib; - Window dummy; - - X11_XGetWindowAttributes(display, windowdata->xwindow, &attrib); - x = attrib.x + (attrib.width - data->dialog_width) / 2; - y = attrib.y + (attrib.height - data->dialog_height) / 3; - X11_XTranslateCoordinates(display, windowdata->xwindow, RootWindow(display, data->screen), x, y, &x, &y, &dummy); + w += (SDL_TOOLKIT_X11_ELEMENT_PADDING_2 * controls->window->iscale) * 2; + if (controls->message->rect.h > controls->icon->rect.h) { + h = controls->message->rect.h; } else { - const SDL_VideoDevice *dev = SDL_GetVideoDevice(); - if (dev && dev->displays && dev->num_displays > 0) { - const SDL_VideoDisplay *dpy = dev->displays[0]; - 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); - } -#ifdef SDL_VIDEO_DRIVER_X11_XRANDR - else if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, use_xrandr_by_default) && data->xrandr) { - XRRScreenResources *screen = X11_XRRGetScreenResourcesCurrent(display, DefaultRootWindow(display)); - if (!screen) { - goto XRANDRBAIL; - } - if (!screen->ncrtc) { - goto XRANDRBAIL; - } - - XRRCrtcInfo *crtc_info = X11_XRRGetCrtcInfo(display, screen, screen->crtcs[0]); - if (crtc_info) { - x = (crtc_info->width - data->dialog_width) / 2; - y = (crtc_info->height - data->dialog_height) / 3; - } else { - goto XRANDRBAIL; - } - } -#endif - else { - // oh well. This will misposition on a multi-head setup. Init first next time. - XRANDRBAIL: - x = (DisplayWidth(display, data->screen) - data->dialog_width) / 2; - y = (DisplayHeight(display, data->screen) - data->dialog_height) / 3; - } + h = controls->icon->rect.h; } - X11_XMoveWindow(display, data->window, x, y); - - sizehints = X11_XAllocSizeHints(); - if (sizehints) { - sizehints->flags = USPosition | USSize | PMaxSize | PMinSize; - sizehints->x = x; - sizehints->y = y; - sizehints->width = data->dialog_width; - sizehints->height = data->dialog_height; - - sizehints->min_width = sizehints->max_width = data->dialog_width; - sizehints->min_height = sizehints->max_height = data->dialog_height; - - X11_XSetWMNormalHints(display, data->window, sizehints); - - X11_XFree(sizehints); + h += max_button_h + (SDL_TOOLKIT_X11_ELEMENT_PADDING_2 * controls->window->iscale) * 3; + t = (w - total_text_and_icon_w) / 2; + controls->icon->rect.x += t; + controls->message->rect.x += t; + controls->icon->rect.y += (SDL_TOOLKIT_X11_ELEMENT_PADDING_2 * controls->window->iscale); + controls->message->rect.y += (SDL_TOOLKIT_X11_ELEMENT_PADDING_2 * controls->window->iscale); + t = (w - total_button_w) / 2; + for (i = 0; i < controls->messageboxdata->numbuttons; i++) { + controls->buttons[i]->rect.x += t; + controls->buttons[i]->rect.y += (SDL_TOOLKIT_X11_ELEMENT_PADDING_2 * controls->window->iscale); + } + if (!controls->messageboxdata->message) { + controls->icon->rect.x = (w - controls->icon->rect.w)/2; } - X11_XMapRaised(display, data->window); - -#ifdef SDL_VIDEO_DRIVER_X11_XDBE - // Initialise a back buffer for double buffering - if (SDL_X11_HAVE_XDBE) { - int xdbe_major, xdbe_minor; - if (X11_XdbeQueryExtension(display, &xdbe_major, &xdbe_minor) != 0) { - data->xdbe = true; - data->buf = X11_XdbeAllocateBackBufferName(display, data->window, XdbeUndefined); - } else { - data->xdbe = false; - } - } -#endif - - return true; + *wp = w; + *hp = h; } -// Draw our message box. -static void X11_MessageBoxDraw(SDL_MessageBoxDataX11 *data, GC ctx, bool utf8) -{ - int i; - Drawable window = data->window; - Display *display = data->display; +static void X11_OnMessageBoxScaleChange(SDL_ToolkitWindowX11 *window, void *data) { + SDL_MessageBoxControlsX11 *controls; + int w; + int h; -#ifdef SDL_VIDEO_DRIVER_X11_XDBE - if (SDL_X11_HAVE_XDBE && data->xdbe) { - window = data->buf; - X11_XdbeBeginIdiom(data->display); - } -#endif - - X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].pixel); - X11_XFillRectangle(display, window, ctx, 0, 0, data->dialog_width, data->dialog_height); - - if(data->icon_char != '\0') { - X11_XSetForeground(display, ctx, data->xcolor_bg_shadow.pixel); - X11_XFillArc(display, window, ctx, data->icon_box_rect.x + 2, data->icon_box_rect.y + 2, data->icon_box_rect.w, data->icon_box_rect.h, 0, 360 * 64); - switch (data->messageboxdata->flags & (SDL_MESSAGEBOX_ERROR | SDL_MESSAGEBOX_WARNING | SDL_MESSAGEBOX_INFORMATION)) { - case SDL_MESSAGEBOX_ERROR: - X11_XSetForeground(display, ctx, data->xcolor_red_darker.pixel); - X11_XFillArc(display, window, ctx, data->icon_box_rect.x, data->icon_box_rect.y, data->icon_box_rect.w, data->icon_box_rect.h, 0, 360 * 64); - X11_XSetForeground(display, ctx, data->xcolor_red.pixel); - X11_XFillArc(display, window, ctx, data->icon_box_rect.x+1, data->icon_box_rect.y+1, data->icon_box_rect.w-2, data->icon_box_rect.h-2, 0, 360 * 64); - X11_XSetForeground(display, ctx, data->xcolor_white.pixel); - break; - case SDL_MESSAGEBOX_WARNING: - X11_XSetForeground(display, ctx, data->xcolor_black.pixel); - X11_XFillArc(display, window, ctx, data->icon_box_rect.x, data->icon_box_rect.y, data->icon_box_rect.w, data->icon_box_rect.h, 0, 360 * 64); - X11_XSetForeground(display, ctx, data->xcolor_yellow.pixel); - X11_XFillArc(display, window, ctx, data->icon_box_rect.x+1, data->icon_box_rect.y+1, data->icon_box_rect.w-2, data->icon_box_rect.h-2, 0, 360 * 64); - X11_XSetForeground(display, ctx, data->xcolor_black.pixel); - break; - case SDL_MESSAGEBOX_INFORMATION: - X11_XSetForeground(display, ctx, data->xcolor_white.pixel); - X11_XFillArc(display, window, ctx, data->icon_box_rect.x, data->icon_box_rect.y, data->icon_box_rect.w, data->icon_box_rect.h, 0, 360 * 64); - X11_XSetForeground(display, ctx, data->xcolor_blue.pixel); - X11_XFillArc(display, window, ctx, data->icon_box_rect.x+1, data->icon_box_rect.y+1, data->icon_box_rect.w-2, data->icon_box_rect.h-2, 0, 360 * 64); - X11_XSetForeground(display, ctx, data->xcolor_white.pixel); - break; - default: - X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].pixel); - X11_XFillArc(display, window, ctx, data->icon_box_rect.x, data->icon_box_rect.y, data->icon_box_rect.w, data->icon_box_rect.h, 0, 360 * 64); - X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].pixel); - } - X11_XSetFont(display, ctx, data->icon_char_font->fid); - X11_XDrawString(display, window, ctx, data->icon_char_x, data->icon_char_y, &data->icon_char, 1); - if (!utf8) { - X11_XSetFont(display, ctx, data->font_struct->fid); - } - } - - X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].pixel); - for (i = 0; i < data->numlines; i++) { - TextLineData *plinedata = &data->linedata[i]; - -#ifdef X_HAVE_UTF8_STRING - if (SDL_X11_HAVE_UTF8) { - X11_Xutf8DrawString(display, window, data->font_set, ctx, - plinedata->rect.x, plinedata->rect.y, - plinedata->text, plinedata->length); - } else -#endif - { - X11_XDrawString(display, window, ctx, - plinedata->rect.x, plinedata->rect.y, - plinedata->text, plinedata->length); - } - } - - X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].pixel); - for (i = 0; i < data->numbuttons; i++) { - SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[i]; - const SDL_MessageBoxButtonData *buttondata = buttondatax11->buttondata; - - /* Draw bevel */ - if (data->button_press_index == i) { - X11_XSetForeground(display, ctx, data->xcolor_bevel_d.pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x, buttondatax11->rect.y, - buttondatax11->rect.w, buttondatax11->rect.h); - - X11_XSetForeground(display, ctx, data->xcolor_bevel_l2.pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x, buttondatax11->rect.y, - buttondatax11->rect.w - 1, buttondatax11->rect.h - 1); - - - X11_XSetForeground(display, ctx, data->xcolor_bevel_l1.pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x + 1, buttondatax11->rect.y + 1, - buttondatax11->rect.w - 3, buttondatax11->rect.h - 2); - - X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x + 1, buttondatax11->rect.y + 1, - buttondatax11->rect.w - 3, buttondatax11->rect.h - 3); - - X11_XSetForeground(display, ctx, data->xcolor_pressed.pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x + 2, buttondatax11->rect.y + 2, - buttondatax11->rect.w - 4, buttondatax11->rect.h - 4); - } else { - if (buttondata->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) { - X11_XSetForeground(display, ctx, data->xcolor_bevel_d.pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x, buttondatax11->rect.y, - buttondatax11->rect.w, buttondatax11->rect.h); - - X11_XSetForeground(display, ctx, data->xcolor_bevel_l2.pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x + 1, buttondatax11->rect.y + 1, - buttondatax11->rect.w - 3, buttondatax11->rect.h - 3); - - X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x + 2, buttondatax11->rect.y + 2, - buttondatax11->rect.w - 4, buttondatax11->rect.h - 4); - - X11_XSetForeground(display, ctx, data->xcolor_bevel_l1.pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x + 2, buttondatax11->rect.y + 2, - buttondatax11->rect.w - 5, buttondatax11->rect.h - 5); - - X11_XSetForeground(display, ctx, (data->mouse_over_index == i) ? data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED].pixel : data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND].pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x + 3, buttondatax11->rect.y + 3, - buttondatax11->rect.w - 6, buttondatax11->rect.h - 6); - } else { - X11_XSetForeground(display, ctx, data->xcolor_bevel_d.pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x, buttondatax11->rect.y, - buttondatax11->rect.w, buttondatax11->rect.h); - - X11_XSetForeground(display, ctx, data->xcolor_bevel_l2.pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x, buttondatax11->rect.y, - buttondatax11->rect.w - 1, buttondatax11->rect.h - 1); - - X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x + 1, buttondatax11->rect.y + 1, - buttondatax11->rect.w - 2, buttondatax11->rect.h - 2); - - X11_XSetForeground(display, ctx, data->xcolor_bevel_l1.pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x + 1, buttondatax11->rect.y + 1, - buttondatax11->rect.w - 3, buttondatax11->rect.h - 3); - - X11_XSetForeground(display, ctx, (data->mouse_over_index == i) ? data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED].pixel : data->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND].pixel); - X11_XFillRectangle(display, window, ctx, - buttondatax11->rect.x + 2, buttondatax11->rect.y + 2, - buttondatax11->rect.w - 4, buttondatax11->rect.h - 4); - } - } - - X11_XSetForeground(display, ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].pixel); -#ifdef X_HAVE_UTF8_STRING - if (SDL_X11_HAVE_UTF8) { - X11_Xutf8DrawString(display, window, data->font_set, ctx, - buttondatax11->text_rect.x, - buttondatax11->text_rect.y, - buttondata->text, buttondatax11->length); - } else -#endif - { - X11_XDrawString(display, window, ctx, - buttondatax11->text_rect.x, buttondatax11->text_rect.y, - buttondata->text, buttondatax11->length); - } - } - -#ifdef SDL_VIDEO_DRIVER_X11_XDBE - if (SDL_X11_HAVE_XDBE && data->xdbe) { - XdbeSwapInfo swap_info; - swap_info.swap_window = data->window; - swap_info.swap_action = XdbeUndefined; - X11_XdbeSwapBuffers(data->display, &swap_info, 1); - X11_XdbeEndIdiom(data->display); - } -#endif -} - -// NOLINTNEXTLINE(readability-non-const-parameter): cannot make XPointer a const pointer due to typedef -static Bool X11_MessageBoxEventTest(Display *display, XEvent *event, XPointer arg) -{ - const SDL_MessageBoxDataX11 *data = (const SDL_MessageBoxDataX11 *)arg; - return ((event->xany.display == data->display) && (event->xany.window == data->window)) ? True : False; -} - -// Loop and handle message box event messages until something kills it. -static bool X11_MessageBoxLoop(SDL_MessageBoxDataX11 *data) -{ - GC ctx; - XGCValues ctx_vals; - bool close_dialog = false; - bool has_focus = true; - KeySym last_key_pressed = XK_VoidSymbol; - unsigned long gcflags = GCForeground | GCBackground; -#ifdef X_HAVE_UTF8_STRING - const int have_utf8 = SDL_X11_HAVE_UTF8; -#else - const int have_utf8 = 0; -#endif - - SDL_zero(ctx_vals); - ctx_vals.foreground = data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].pixel; - ctx_vals.background = data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].pixel; - - if (!have_utf8) { - gcflags |= GCFont; - ctx_vals.font = data->font_struct->fid; - } - - ctx = X11_XCreateGC(data->display, data->window, gcflags, &ctx_vals); - if (ctx == None) { - return SDL_SetError("Couldn't create graphics context"); - } - - data->button_press_index = -1; // Reset what button is currently depressed. - data->mouse_over_index = -1; // Reset what button the mouse is over. - - while (!close_dialog) { - XEvent e; - bool draw = true; - - // can't use XWindowEvent() because it can't handle ClientMessage events. - // can't use XNextEvent() because we only want events for this window. - X11_XIfEvent(data->display, &e, X11_MessageBoxEventTest, (XPointer)data); - - /* If X11_XFilterEvent returns True, then some input method has filtered the - event, and the client should discard the event. */ - if ((e.type != Expose) && X11_XFilterEvent(&e, None)) { - continue; - } - - switch (e.type) { - case Expose: - if (e.xexpose.count > 0) { - draw = false; - } - break; - - case FocusIn: - // Got focus. - has_focus = true; - break; - - case FocusOut: - // lost focus. Reset button and mouse info. - has_focus = false; - data->button_press_index = -1; - data->mouse_over_index = -1; - break; - - case MotionNotify: - if (has_focus) { - // Mouse moved... - const int previndex = data->mouse_over_index; - data->mouse_over_index = GetHitButtonIndex(data, e.xbutton.x, e.xbutton.y); - if (data->mouse_over_index == previndex) { - draw = false; - } - } - break; - - case ClientMessage: - if (e.xclient.message_type == data->wm_protocols && - e.xclient.format == 32 && - e.xclient.data.l[0] == data->wm_delete_message) { - close_dialog = true; - } - break; - - case KeyPress: - // Store key press - we make sure in key release that we got both. - last_key_pressed = X11_XLookupKeysym(&e.xkey, 0); - break; - - case KeyRelease: - { - Uint32 mask = 0; - KeySym key = X11_XLookupKeysym(&e.xkey, 0); - - // If this is a key release for something we didn't get the key down for, then bail. - if (key != last_key_pressed) { - break; - } - - if (key == XK_Escape) { - mask = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT; - } else if ((key == XK_Return) || (key == XK_KP_Enter)) { - mask = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT; - } - - if (mask) { - int i; - - // Look for first button with this mask set, and return it if found. - for (i = 0; i < data->numbuttons; i++) { - SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[i]; - - if (buttondatax11->buttondata->flags & mask) { - *data->pbuttonid = buttondatax11->buttondata->buttonID; - close_dialog = true; - break; - } - } - } - break; - } - - case ButtonPress: - data->button_press_index = -1; - if (e.xbutton.button == Button1) { - // Find index of button they clicked on. - data->button_press_index = GetHitButtonIndex(data, e.xbutton.x, e.xbutton.y); - } - break; - - case ButtonRelease: - // If button is released over the same button that was clicked down on, then return it. - if ((e.xbutton.button == Button1) && (data->button_press_index >= 0)) { - int button = GetHitButtonIndex(data, e.xbutton.x, e.xbutton.y); - - if (data->button_press_index == button) { - SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[button]; - - *data->pbuttonid = buttondatax11->buttondata->buttonID; - close_dialog = true; - } - } - data->button_press_index = -1; - break; - } - - if (draw) { - // Draw our dialog box. - X11_MessageBoxDraw(data, ctx, have_utf8); - } - } - - X11_XFreeGC(data->display, ctx); - return true; + controls = data; + X11_PositionMessageBox(controls, &w, &h); + X11Toolkit_ResizeWindow(window, w, h); } static bool X11_ShowMessageBoxImpl(const SDL_MessageBoxData *messageboxdata, int *buttonID) { - bool result = false; - SDL_MessageBoxDataX11 data; -#if SDL_SET_LOCALE - char *origlocale; -#endif + SDL_MessageBoxControlsX11 controls; + SDL_MessageBoxCallbackDataX11 data; + const SDL_MessageBoxColor *colorhints; + int i; + int w; + int h; - SDL_zero(data); + controls.messageboxdata = messageboxdata; - if (!SDL_X11_LoadSymbols()) { + /* Color scheme */ + if (messageboxdata->colorScheme) { + colorhints = messageboxdata->colorScheme->colors; + } else { + colorhints = NULL; + } + + /* Create window */ + controls.window = X11Toolkit_CreateWindowStruct(messageboxdata->window, NULL, SDL_TOOLKIT_WINDOW_MODE_X11_DIALOG, colorhints); + controls.window->cb_data = &controls; + controls.window->cb_on_scale_change = X11_OnMessageBoxScaleChange; + if (!controls.window) { return false; } -#if SDL_SET_LOCALE - origlocale = setlocale(LC_ALL, NULL); - if (origlocale) { - origlocale = SDL_strdup(origlocale); - if (!origlocale) { - return false; - } - (void)setlocale(LC_ALL, ""); + /* Create controls */ + controls.buttons = SDL_calloc(messageboxdata->numbuttons, sizeof(SDL_ToolkitControlX11 *)); + controls.icon = X11Toolkit_CreateIconControl(controls.window, messageboxdata->flags); + controls.message = X11Toolkit_CreateLabelControl(controls.window, (char *)messageboxdata->message); + data.buttonID = buttonID; + data.window = controls.window; + for (i = 0; i < messageboxdata->numbuttons; i++) { + controls.buttons[i] = X11Toolkit_CreateButtonControl(controls.window, &messageboxdata->buttons[i]); + X11Toolkit_RegisterCallbackForButtonControl(controls.buttons[i], &data, X11_MessageBoxButtonCallback); } -#endif - // This code could get called from multiple threads maybe? - X11_XInitThreads(); + /* Positioning */ + X11_PositionMessageBox(&controls, &w, &h); - // Initialize the return buttonID value to -1 (for error or dialogbox closed). - *buttonID = -1; - - // Init and display the message box. - if (!X11_MessageBoxInit(&data, messageboxdata, buttonID)) { - goto done; + /* Actually create window, do event loop, cleanup */ + X11Toolkit_CreateWindowRes(controls.window, w, h, 0, 0, (char *)messageboxdata->title); + X11Toolkit_DoWindowEventLoop(controls.window); + X11Toolkit_DestroyWindow(controls.window); + if (controls.buttons) { + SDL_free(controls.buttons); } - - if (!X11_MessageBoxInitPositions(&data)) { - goto done; - } - - if (!X11_MessageBoxCreateWindow(&data)) { - goto done; - } - - result = X11_MessageBoxLoop(&data); - -done: - X11_MessageBoxShutdown(&data); -#if SDL_SET_LOCALE - if (origlocale) { - (void)setlocale(LC_ALL, origlocale); - SDL_free(origlocale); - } -#endif - - return result; + return true; } // Display an x11 message box. diff --git a/src/video/x11/SDL_x11settings.c b/src/video/x11/SDL_x11settings.c index 50bf773f3a..da8b794405 100644 --- a/src/video/x11/SDL_x11settings.c +++ b/src/video/x11/SDL_x11settings.c @@ -26,10 +26,6 @@ #include "SDL_x11video.h" #include "SDL_x11settings.h" -#define SDL_XSETTINGS_GDK_WINDOW_SCALING_FACTOR "Gdk/WindowScalingFactor" -#define SDL_XSETTINGS_GDK_UNSCALED_DPI "Gdk/UnscaledDPI" -#define SDL_XSETTINGS_XFT_DPI "Xft/DPI" - static void UpdateContentScale(SDL_VideoDevice *_this) { if (_this) { @@ -45,7 +41,7 @@ static void X11_XsettingsNotify(const char *name, XSettingsAction action, XSetti SDL_VideoDevice *_this = data; if (SDL_strcmp(name, SDL_XSETTINGS_GDK_WINDOW_SCALING_FACTOR) == 0 || - SDL_strcmp(name, SDL_XSETTINGS_GDK_UNSCALED_DPI) == 0 || + SDL_strcmp(name, SDL_XSETTINGS_GDK_UNSCALED_DPI) == 0 || SDL_strcmp(name, SDL_XSETTINGS_XFT_DPI) == 0) { UpdateContentScale(_this); } diff --git a/src/video/x11/SDL_x11settings.h b/src/video/x11/SDL_x11settings.h index f91ed70ac7..2dab71e058 100644 --- a/src/video/x11/SDL_x11settings.h +++ b/src/video/x11/SDL_x11settings.h @@ -27,6 +27,10 @@ #include #include "xsettings-client.h" +#define SDL_XSETTINGS_GDK_WINDOW_SCALING_FACTOR "Gdk/WindowScalingFactor" +#define SDL_XSETTINGS_GDK_UNSCALED_DPI "Gdk/UnscaledDPI" +#define SDL_XSETTINGS_XFT_DPI "Xft/DPI" + typedef struct X11_SettingsData { XSettingsClient *xsettings; } SDLX11_SettingsData; diff --git a/src/video/x11/SDL_x11sym.h b/src/video/x11/SDL_x11sym.h index 959aabce8c..07f2b4539f 100644 --- a/src/video/x11/SDL_x11sym.h +++ b/src/video/x11/SDL_x11sym.h @@ -42,6 +42,7 @@ SDL_X11_SYM(int,XConvertSelection,(Display* a,Atom b,Atom c,Atom d,Window e,Time SDL_X11_SYM(Pixmap,XCreateBitmapFromData,(Display *dpy,Drawable d,_Xconst char *data,unsigned int width,unsigned int height)) SDL_X11_SYM(Colormap,XCreateColormap,(Display* a,Window b,Visual* c,int d)) SDL_X11_SYM(Cursor,XCreatePixmapCursor,(Display* a,Pixmap b,Pixmap c,XColor* d,XColor* e,unsigned int f,unsigned int g)) +SDL_X11_SYM(Cursor,XCreatePixmap,(Display* a,Drawable b,unsigned int d,unsigned int e,unsigned int f)) SDL_X11_SYM(Cursor,XCreateFontCursor,(Display* a,unsigned int b)) SDL_X11_SYM(XFontSet,XCreateFontSet,(Display* a, _Xconst char* b, char*** c, int* d, char** e)) SDL_X11_SYM(GC,XCreateGC,(Display* a,Drawable b,unsigned long c,XGCValues* d)) @@ -68,6 +69,7 @@ SDL_X11_SYM(int,XFreeGC,(Display* a,GC b)) SDL_X11_SYM(int,XFreeFont,(Display* a, XFontStruct* b)) SDL_X11_SYM(int,XFreeModifiermap,(XModifierKeymap* a)) SDL_X11_SYM(int,XFreePixmap,(Display* a,Pixmap b)) +SDL_X11_SYM(int,XFreeColormap,(Display* a,Colormap b)) SDL_X11_SYM(void,XFreeStringList,(char** a)) SDL_X11_SYM(char*,XGetAtomName,(Display *a,Atom b)) SDL_X11_SYM(int,XGetInputFocus,(Display *a,Window *b,int *c)) @@ -102,6 +104,7 @@ SDL_X11_SYM(Display*,XOpenDisplay,(_Xconst char* a)) SDL_X11_SYM(Status,XInitThreads,(void)) SDL_X11_SYM(int,XPeekEvent,(Display* a,XEvent* b)) SDL_X11_SYM(int,XPending,(Display* a)) +SDL_X11_SYM(XImage*,XGetImage,(Display* a,Drawable b,int c, int d,unsigned int e,unsigned int f,unsigned long g,int h)) SDL_X11_SYM(int,XPutImage,(Display* a,Drawable b,GC c,XImage* d,int e,int f,int g,int h,unsigned int i,unsigned int j)) SDL_X11_SYM(int,XQueryKeymap,(Display* a,char b[32])) SDL_X11_SYM(Bool,XQueryPointer,(Display* a,Window b,Window* c,Window* d,int* e,int* f,int* g,int* h,unsigned int* i)) diff --git a/src/video/x11/SDL_x11toolkit.c b/src/video/x11/SDL_x11toolkit.c new file mode 100644 index 0000000000..1e1d50e663 --- /dev/null +++ b/src/video/x11/SDL_x11toolkit.c @@ -0,0 +1,1862 @@ +/* + 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_internal.h" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include "../../SDL_list.h" +#include "SDL_x11video.h" +#include "SDL_x11dyn.h" +#include "SDL_x11toolkit.h" +#include "SDL_x11settings.h" +#include "SDL_x11modes.h" +#include "xsettings-client.h" +#include +#include + +#define SDL_SET_LOCALE 1 +#define SDL_GRAB 1 + +typedef struct SDL_ToolkitIconControlX11 +{ + SDL_ToolkitControlX11 parent; + + /* Icon type */ + SDL_MessageBoxFlags flags; + char icon_char; + + /* Font */ + XFontStruct *icon_char_font; + int icon_char_x; + int icon_char_y; + int icon_char_a; + + /* Colors */ + XColor xcolor_black; + XColor xcolor_red; + XColor xcolor_red_darker; + XColor xcolor_white; + XColor xcolor_yellow; + XColor xcolor_blue; + XColor xcolor_bg_shadow; +} SDL_ToolkitIconControlX11; + +typedef struct SDL_ToolkitButtonControlX11 +{ + SDL_ToolkitControlX11 parent; + + /* Data */ + const SDL_MessageBoxButtonData *data; + + /* Text */ + SDL_Rect text_rect; + int text_a; + int text_d; + int str_sz; + + /* Callback */ + void *cb_data; + void (*cb)(struct SDL_ToolkitControlX11 *, void *); +} SDL_ToolkitButtonControlX11; + +typedef struct SDL_ToolkitLabelControlX11 +{ + SDL_ToolkitControlX11 parent; + + char **lines; + int *y; + size_t *szs; + size_t sz; +} SDL_ToolkitLabelControlX11; + +typedef struct SDL_ToolkitMenuBarControlX11 +{ + SDL_ToolkitControlX11 parent; + + SDL_ListNode *menu_items; +} SDL_ToolkitMenuBarControlX11; + +typedef struct SDL_ToolkitMenuControlX11 +{ + SDL_ToolkitControlX11 parent; + + SDL_ListNode *menu_items; + XColor xcolor_check_bg; +} SDL_ToolkitMenuControlX11; + +/* Font for icon control */ +static const char *g_IconFont = "-*-*-bold-r-normal-*-%d-*-*-*-*-*-iso8859-1[33 88 105]"; +#define G_ICONFONT_SIZE 18 + +/* General UI font */ +static const char g_ToolkitFontLatin1[] = + "-*-*-medium-r-normal--0-%d-*-*-p-0-iso8859-1"; +static const char *g_ToolkitFont[] = { + "-*-*-medium-r-normal--*-%d-*-*-*-*-iso10646-1", // explicitly unicode (iso10646-1) + "-*-*-medium-r-*--*-%d-*-*-*-*-iso10646-1", // explicitly unicode (iso10646-1) + "-misc-*-*-*-*--*-*-*-*-*-*-iso10646-1", // misc unicode (fix for some systems) + "-*-*-*-*-*--*-*-*-*-*-*-iso10646-1", // just give me anything Unicode. + "-*-*-medium-r-normal--*-v-*-*-*-*-iso8859-1", // explicitly latin1, in case low-ASCII works out. + "-*-*-medium-r-*--*-%d-*-*-*-*-iso8859-1", // explicitly latin1, in case low-ASCII works out. + "-misc-*-*-*-*--*-*-*-*-*-*-iso8859-1", // misc latin1 (fix for some systems) + "-*-*-*-*-*--*-*-*-*-*-*-iso8859-1", // just give me anything latin1. + NULL +}; +#define G_TOOLKITFONT_SIZE 120 + +static const SDL_MessageBoxColor g_default_colors[SDL_MESSAGEBOX_COLOR_COUNT] = { + { 191, 184, 191 }, // SDL_MESSAGEBOX_COLOR_BACKGROUND, + { 0, 0, 0 }, // SDL_MESSAGEBOX_COLOR_TEXT, + { 127, 120, 127 }, // SDL_MESSAGEBOX_COLOR_BUTTON_BORDER, + { 191, 184, 191 }, // SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND, + { 235, 235, 235 }, // SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED, +}; + +int X11Toolkit_SettingsGetInt(XSettingsClient *client, const char *key, int fallback_value) { + XSettingsSetting *setting = NULL; + int res = fallback_value; + + if (client) { + if (xsettings_client_get_setting(client, key, &setting) != XSETTINGS_SUCCESS) { + goto no_key; + } + + if (setting->type != XSETTINGS_TYPE_INT) { + goto no_key; + } + + res = setting->data.v_int; + } + +no_key: + if (setting) { + xsettings_setting_free(setting); + } + + return res; +} + +static float X11Toolkit_GetUIScale(XSettingsClient *client, Display *display) +{ + double scale_factor = 0.0; + + // First use the forced scaling factor specified by the app/user + const char *hint = SDL_GetHint(SDL_HINT_VIDEO_X11_SCALING_FACTOR); + if (hint && *hint) { + double value = SDL_atof(hint); + if (value >= 1.0f && value <= 10.0f) { + scale_factor = value; + } + } + + // If that failed, try "Xft.dpi" from the XResourcesDatabase... + // We attempt to read this directly to get the live value, XResourceManagerString + // is cached per display connection. + if (scale_factor <= 0.0) { + int status, real_format; + Atom real_type; + Atom res_mgr; + unsigned long items_read, items_left; + char *resource_manager; + bool owns_resource_manager = false; + + X11_XrmInitialize(); + res_mgr = X11_XInternAtom(display, "RESOURCE_MANAGER", False); + status = X11_XGetWindowProperty(display, RootWindow(display, DefaultScreen(display)), + res_mgr, 0L, 8192L, False, XA_STRING, + &real_type, &real_format, &items_read, &items_left, + (unsigned char **)&resource_manager); + + if (status == Success && resource_manager) { + owns_resource_manager = true; + } else { + // Fall back to XResourceManagerString. This will not be updated if the + // dpi value is later changed but should allow getting the initial value. + resource_manager = X11_XResourceManagerString(display); + } + + if (resource_manager) { + XrmDatabase db; + XrmValue value; + char *type; + + db = X11_XrmGetStringDatabase(resource_manager); + + // Get the value of Xft.dpi from the Database + if (X11_XrmGetResource(db, "Xft.dpi", "String", &type, &value)) { + if (value.addr && type && SDL_strcmp(type, "String") == 0) { + int dpi = SDL_atoi(value.addr); + scale_factor = dpi / 96.0; + } + } + X11_XrmDestroyDatabase(db); + + if (owns_resource_manager) { + X11_XFree(resource_manager); + } + } + } + + // If that failed, try the XSETTINGS keys... + if (scale_factor <= 0.0) { + scale_factor = X11Toolkit_SettingsGetInt(client, "Gdk/WindowScalingFactor", -1); + + // The Xft/DPI key is stored in increments of 1024th + if (scale_factor <= 0.0) { + int dpi = X11Toolkit_SettingsGetInt(client, "Xft/DPI", -1); + if (dpi > 0) { + scale_factor = (double) dpi / 1024.0; + scale_factor /= 96.0; + } + } + } + + // If that failed, try the GDK_SCALE envvar... + if (scale_factor <= 0.0) { + const char *scale_str = SDL_getenv("GDK_SCALE"); + if (scale_str) { + scale_factor = SDL_atoi(scale_str); + } + } + + // Nothing or a bad value, just fall back to 1.0 + if (scale_factor <= 0.0) { + scale_factor = 1.0; + } + + return (float)scale_factor; +} + +static void X11Toolkit_SettingsNotify(const char *name, XSettingsAction action, XSettingsSetting *setting, void *data) +{ + SDL_ToolkitWindowX11 *window; + int i; + + window = data; + + if (window->xsettings_first_time) { + return; + } + + if (SDL_strcmp(name, SDL_XSETTINGS_GDK_WINDOW_SCALING_FACTOR) == 0 || + SDL_strcmp(name, SDL_XSETTINGS_GDK_UNSCALED_DPI) == 0 || + SDL_strcmp(name, SDL_XSETTINGS_XFT_DPI) == 0) { + bool dbe_already_setup; + bool pixmap_already_setup; + + if (window->pixmap) { + pixmap_already_setup = true; + } else { + dbe_already_setup = true; + } + + /* set scale vars */ + window->scale = X11Toolkit_GetUIScale(window->xsettings, window->display); + window->iscale = (int)SDL_ceilf(window->scale); + if (roundf(window->scale) == window->scale) { + window->scale = 0; + } + + /* set up window */ + if (window->scale != 0) { + window->window_width = SDL_lroundf((window->window_width/window->iscale) * window->scale); + window->window_height = SDL_lroundf((window->window_height/window->iscale) * window->scale); + window->pixmap_width = window->window_width; + window->pixmap_height = window->window_height; + window->pixmap = true; + } else { + window->pixmap = false; + } + + if (window->pixmap) { + if (!pixmap_already_setup) { +#ifdef SDL_VIDEO_DRIVER_X11_XDBE + if (SDL_X11_HAVE_XDBE && window->xdbe) { + X11_XdbeDeallocateBackBufferName(window->display, window->buf); + } +#endif + } + + X11_XFreePixmap(window->display, window->drawable); + window->drawable = X11_XCreatePixmap(window->display, window->window, window->pixmap_width, window->pixmap_height, window->depth); + } else { + if (!dbe_already_setup) { + X11_XFreePixmap(window->display, window->drawable); +#ifdef SDL_VIDEO_DRIVER_X11_XDBE + if (SDL_X11_HAVE_XDBE && window->xdbe) { + window->buf = X11_XdbeAllocateBackBufferName(window->display, window->window, XdbeUndefined); + window->drawable = window->buf; + } +#endif + } + } + + /* setup fonts */ +#ifdef X_HAVE_UTF8_STRING + if (window->font_set) { + X11_XFreeFontSet(window->display, window->font_set); + } +#endif + if (window->font_struct) { + X11_XFreeFont(window->display, window->font_struct); + } + +#ifdef X_HAVE_UTF8_STRING + window->utf8 = true; + window->font_set = NULL; + if (SDL_X11_HAVE_UTF8) { + char **missing = NULL; + int num_missing = 0; + int i_font; + window->font_struct = NULL; + for (i_font = 0; g_ToolkitFont[i_font]; ++i_font) { + char *font; + + SDL_asprintf(&font, g_ToolkitFont[i_font], G_TOOLKITFONT_SIZE * window->iscale); + window->font_set = X11_XCreateFontSet(window->display, font, + &missing, &num_missing, NULL); + SDL_free(font); + if (missing) { + X11_XFreeStringList(missing); + } + if (window->font_set) { + break; + } + } + if (!window->font_set) { + goto load_font_traditional; + } + } else +#endif + { + char *font; + load_font_traditional: + + SDL_asprintf(&font, g_ToolkitFontLatin1, G_TOOLKITFONT_SIZE * window->iscale); + window->font_struct = X11_XLoadQueryFont(window->display, font); + SDL_free(font); + window->utf8 = false; + } + + /* notify controls */ + for (i = 0; i < window->controls_sz; i++) { + if (window->controls[i]->func_on_scale_change) { + window->controls[i]->func_on_scale_change(window->controls[i]); + } + + if (window->controls[i]->func_calc_size) { + window->controls[i]->func_calc_size(window->controls[i]); + } + } + + /* notify cb */ + if (window->cb_on_scale_change) { + window->cb_on_scale_change(window, window->cb_data); + } + + /* update ev scales */ + if (!window->pixmap) { + window->ev_scale = window->ev_iscale = 1; + } else { + window->ev_scale = window->scale; + window->ev_iscale = window->iscale; + } + } +} + + +static void X11Toolkit_GetTextWidthHeightForFont(XFontStruct *font, const char *str, int nbytes, int *pwidth, int *pheight, int *font_ascent) +{ + XCharStruct text_structure; + int font_direction, font_descent; + X11_XTextExtents(font, str, nbytes, + &font_direction, font_ascent, &font_descent, + &text_structure); + *pwidth = text_structure.width; + *pheight = text_structure.ascent + text_structure.descent; +} + +static void X11Toolkit_GetTextWidthHeight(SDL_ToolkitWindowX11 *data, const char *str, int nbytes, int *pwidth, int *pheight, int *font_ascent, int *font_descent) +{ +#ifdef X_HAVE_UTF8_STRING + if (data->utf8) { + XFontSetExtents *extents; + XRectangle overall_ink, overall_logical; + extents = X11_XExtentsOfFontSet(data->font_set); + X11_Xutf8TextExtents(data->font_set, str, nbytes, &overall_ink, &overall_logical); + *pwidth = overall_logical.width; + *pheight = overall_logical.height; + *font_ascent = -extents->max_logical_extent.y; + *font_descent = extents->max_logical_extent.height - *font_ascent; + } else +#endif + { + XCharStruct text_structure; + int font_direction; + X11_XTextExtents(data->font_struct, str, nbytes, + &font_direction, font_ascent, font_descent, + &text_structure); + *pwidth = text_structure.width; + *pheight = text_structure.ascent + text_structure.descent; + } +} + +SDL_ToolkitWindowX11 *X11Toolkit_CreateWindowStruct(SDL_Window *parent, SDL_ToolkitWindowX11 *tkparent, SDL_ToolkitWindowModeX11 mode, const SDL_MessageBoxColor *colorhints) +{ + SDL_ToolkitWindowX11 *window; + int i; + #define ErrorFreeRetNull(x, y) SDL_SetError(x); SDL_free(y); return NULL; + #define ErrorCloseFreeRetNull(x, y, z) X11_XCloseDisplay(z->display); SDL_SetError(x, y); SDL_free(z); return NULL; + + if (!SDL_X11_LoadSymbols()) { + return NULL; + } + + // This code could get called from multiple threads maybe? + X11_XInitThreads(); + + window = (SDL_ToolkitWindowX11 *)SDL_malloc(sizeof(SDL_ToolkitWindowX11)); + if (!window) { + SDL_SetError("Unable to allocate toolkit window structure"); + return NULL; + } + + window->mode = mode; + window->tk_parent = tkparent; + +#if SDL_SET_LOCALE + if (mode == SDL_TOOLKIT_WINDOW_MODE_X11_DIALOG) { + window->origlocale = setlocale(LC_ALL, NULL); + if (window->origlocale) { + window->origlocale = SDL_strdup(window->origlocale); + if (!window->origlocale) { + return NULL; + } + (void)setlocale(LC_ALL, ""); + } + } +#endif + + if (parent) { + SDL_VideoData *videodata = SDL_GetVideoDevice()->internal; + window->display = videodata->display; + window->display_close = false; + } else if (tkparent) { + window->display = tkparent->display; + window->display_close = false; + } else { + window->display = X11_XOpenDisplay(NULL); + window->display_close = true; + if (!window->display) { + ErrorFreeRetNull("Couldn't open X11 display", window); + } + } + +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + int xrandr_event_base, xrandr_error_base; + window->xrandr = X11_XRRQueryExtension(window->display, &xrandr_event_base, &xrandr_error_base); +#endif + + /* Scale/Xsettings */ + window->pixmap = false; + window->xsettings_first_time = true; + window->xsettings = xsettings_client_new(window->display, DefaultScreen(window->display), X11Toolkit_SettingsNotify, NULL, window); + window->xsettings_first_time = false; + window->scale = X11Toolkit_GetUIScale(window->xsettings, window->display); + window->iscale = (int)SDL_ceilf(window->scale); + if (roundf(window->scale) == window->scale) { + window->scale = 0; + } + +#ifdef X_HAVE_UTF8_STRING + window->utf8 = true; + window->font_set = NULL; + if (SDL_X11_HAVE_UTF8) { + char **missing = NULL; + int num_missing = 0; + int i_font; + window->font_struct = NULL; + for (i_font = 0; g_ToolkitFont[i_font]; ++i_font) { + char *font; + + SDL_asprintf(&font, g_ToolkitFont[i_font], G_TOOLKITFONT_SIZE * window->iscale); + window->font_set = X11_XCreateFontSet(window->display, font, + &missing, &num_missing, NULL); + SDL_free(font); + if (missing) { + X11_XFreeStringList(missing); + } + if (window->font_set) { + break; + } + } + if (!window->font_set) { + goto load_font_traditional; + } + } else +#endif + { + char *font; + load_font_traditional: + + SDL_asprintf(&font, g_ToolkitFontLatin1, G_TOOLKITFONT_SIZE * window->iscale); + window->font_struct = X11_XLoadQueryFont(window->display, font); + SDL_free(font); + window->utf8 = false; + if (!window->font_struct) { + ErrorCloseFreeRetNull("Couldn't load font %s", g_ToolkitFontLatin1, window); + } + } + + if (!colorhints) { + colorhints = g_default_colors; + } + window->color_hints = colorhints; + + // Convert colors to 16 bpc XColor format + for (i = 0; i < SDL_MESSAGEBOX_COLOR_COUNT; i++) { + window->xcolor[i].flags = DoRed|DoGreen|DoBlue; + window->xcolor[i].red = colorhints[i].r * 257; + window->xcolor[i].green = colorhints[i].g * 257; + window->xcolor[i].blue = colorhints[i].b * 257; + } + + /* Generate bevel and pressed colors */ + window->xcolor_bevel_l1.flags = DoRed|DoGreen|DoBlue; + window->xcolor_bevel_l1.red = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].red + 12500, 0, 65535); + window->xcolor_bevel_l1.green = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].green + 12500, 0, 65535); + window->xcolor_bevel_l1.blue = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].blue + 12500, 0, 65535); + + window->xcolor_bevel_l2.flags = DoRed|DoGreen|DoBlue; + window->xcolor_bevel_l2.red = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].red + 32500, 0, 65535); + window->xcolor_bevel_l2.green = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].green + 32500, 0, 65535); + window->xcolor_bevel_l2.blue = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].blue + 32500, 0, 65535); + + window->xcolor_bevel_d.flags = DoRed|DoGreen|DoBlue; + window->xcolor_bevel_d.red = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].red - 22500, 0, 65535); + window->xcolor_bevel_d.green = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].green - 22500, 0, 65535); + window->xcolor_bevel_d.blue = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].blue - 22500, 0, 65535); + + window->xcolor_pressed.flags = DoRed|DoGreen|DoBlue; + window->xcolor_pressed.red = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND].red - 12500, 0, 65535); + window->xcolor_pressed.green = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND].green - 12500, 0, 65535); + window->xcolor_pressed.blue = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND].blue - 12500, 0, 65535); + + window->xcolor_disabled_text.flags = DoRed|DoGreen|DoBlue; + window->xcolor_disabled_text.red = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].red + 19500, 0, 65535); + window->xcolor_disabled_text.green = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].green + 19500, 0, 65535); + window->xcolor_disabled_text.blue = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].blue + 19500, 0, 65535); + + /* Screen */ + window->parent = parent; + if (parent) { + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(parent); + window->screen = displaydata->screen; + } else { + window->screen = DefaultScreen(window->display); + } + + /* Visuals */ + if (mode == SDL_TOOLKIT_WINDOW_MODE_X11_CHILD) { + window->visual = parent->internal->visual; + window->cmap = parent->internal->colormap; + X11_GetVisualInfoFromVisual(window->display, window->visual, &window->vi); + window->depth = window->vi.depth; + } else { + window->visual = DefaultVisual(window->display, window->screen); + window->cmap = DefaultColormap(window->display, window->screen); + window->depth = DefaultDepth(window->display, window->screen); + X11_GetVisualInfoFromVisual(window->display, window->visual, &window->vi); + } + + /* Allocate colors */ + for (i = 0; i < SDL_MESSAGEBOX_COLOR_COUNT; i++) { + X11_XAllocColor(window->display, window->cmap, &window->xcolor[i]); + } + X11_XAllocColor(window->display, window->cmap, &window->xcolor_bevel_l1); + X11_XAllocColor(window->display, window->cmap, &window->xcolor_bevel_l2); + X11_XAllocColor(window->display, window->cmap, &window->xcolor_bevel_d); + X11_XAllocColor(window->display, window->cmap, &window->xcolor_pressed); + X11_XAllocColor(window->display, window->cmap, &window->xcolor_disabled_text); + + /* Control list */ + window->has_focus = false; + window->controls = NULL; + window->controls_sz = 0; + window->dyn_controls_sz = 0; + window->fiddled_control = NULL; + window->dyn_controls = NULL; + + /* Menu windows */ + window->popup_windows = NULL; + + return window; +} + +static void X11Toolkit_AddControlToWindow(SDL_ToolkitWindowX11 *window, SDL_ToolkitControlX11 *control) { + /* Add to controls list */ + window->controls_sz++; + if (window->controls_sz == 1) { + window->controls = (struct SDL_ToolkitControlX11 **)SDL_malloc(sizeof(struct SDL_ToolkitControlX11 *)); + } else { + window->controls = (struct SDL_ToolkitControlX11 **)SDL_realloc(window->controls, sizeof(struct SDL_ToolkitControlX11 *) * window->controls_sz); + } + window->controls[window->controls_sz - 1] = control; + + /* If dynamic, add it to the dynamic controls list too */ + if (control->dynamic) { + window->dyn_controls_sz++; + if (window->dyn_controls_sz == 1) { + window->dyn_controls = (struct SDL_ToolkitControlX11 **)SDL_malloc(sizeof(struct SDL_ToolkitControlX11 *)); + } else { + window->dyn_controls = (struct SDL_ToolkitControlX11 **)SDL_realloc(window->dyn_controls, sizeof(struct SDL_ToolkitControlX11 *) * window->dyn_controls_sz); + } + window->dyn_controls[window->dyn_controls_sz - 1] = control; + } + + /* If selected, set currently focused control to it */ + if (control->selected) { + window->focused_control = control; + } +} + +bool X11Toolkit_CreateWindowRes(SDL_ToolkitWindowX11 *data, int w, int h, int cx, int cy, char *title) +{ + int x, y; + XSizeHints *sizehints; + XSetWindowAttributes wnd_attr; + Atom _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, _NET_WM_WINDOW_TYPE_TOOLTIP; + SDL_WindowData *windowdata = NULL; + Display *display = data->display; + XGCValues ctx_vals; + Window root_win; + Window parent_win; + unsigned long gcflags = GCForeground | GCBackground; + unsigned long valuemask; +#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 (data->scale == 0) { + data->window_width = w; + data->window_height = h; + } else { + data->window_width = SDL_lroundf((w/data->iscale) * data->scale); + data->window_height = SDL_lroundf((h/data->iscale) * data->scale); + data->pixmap_width = w; + data->pixmap_height = h; + data->pixmap = true; + } + + if (data->parent) { + windowdata = data->parent->internal; + } + + valuemask = CWEventMask | CWColormap; + data->event_mask = ExposureMask | + ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | + StructureNotifyMask | FocusChangeMask | PointerMotionMask; + wnd_attr.event_mask = data->event_mask; + wnd_attr.colormap = data->cmap; + if (data->mode == SDL_TOOLKIT_WINDOW_MODE_X11_MENU || data->mode== SDL_TOOLKIT_WINDOW_MODE_X11_TOOLTIP) { + valuemask |= CWOverrideRedirect | CWSaveUnder; + wnd_attr.save_under = True; + wnd_attr.override_redirect = True; + } + root_win = RootWindow(display, data->screen); + if (data->mode == SDL_TOOLKIT_WINDOW_MODE_X11_CHILD) { + parent_win = windowdata->xwindow; + } else { + parent_win = root_win; + } + + data->window = X11_XCreateWindow( + display, parent_win, + 0, 0, + data->window_width, data->window_height, + 0, data->depth, InputOutput, data->visual, + valuemask, &wnd_attr); + if (data->window == None) { + return SDL_SetError("Couldn't create X window"); + } + + if (windowdata && data->mode == SDL_TOOLKIT_WINDOW_MODE_X11_DIALOG) { + Atom _NET_WM_STATE = X11_XInternAtom(display, "_NET_WM_STATE", False); + Atom stateatoms[16]; + size_t statecount = 0; + // Set some message-boxy window states when attached to a parent window... + // we skip the taskbar since this will pop to the front when the parent window is clicked in the taskbar, etc + stateatoms[statecount++] = X11_XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", False); + stateatoms[statecount++] = X11_XInternAtom(display, "_NET_WM_STATE_SKIP_PAGER", False); + stateatoms[statecount++] = X11_XInternAtom(display, "_NET_WM_STATE_FOCUSED", False); + stateatoms[statecount++] = X11_XInternAtom(display, "_NET_WM_STATE_MODAL", False); + SDL_assert(statecount <= SDL_arraysize(stateatoms)); + X11_XChangeProperty(display, data->window, _NET_WM_STATE, XA_ATOM, 32, + PropModeReplace, (unsigned char *)stateatoms, statecount); + } + + if (windowdata && data->mode != SDL_TOOLKIT_WINDOW_MODE_X11_CHILD) { + X11_XSetTransientForHint(display, data->window, windowdata->xwindow); + } + + if (data->tk_parent) { + X11_XSetTransientForHint(display, data->window, data->tk_parent->window); + } + + SDL_X11_SetWindowTitle(display, data->window, title); + + // Let the window manager the type of the window + if (data->mode == SDL_TOOLKIT_WINDOW_MODE_X11_DIALOG) { + _NET_WM_WINDOW_TYPE = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); + _NET_WM_WINDOW_TYPE_DIALOG = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", False); + X11_XChangeProperty(display, data->window, _NET_WM_WINDOW_TYPE, XA_ATOM, 32, + PropModeReplace, + (unsigned char *)&_NET_WM_WINDOW_TYPE_DIALOG, 1); + } else if (data->mode == SDL_TOOLKIT_WINDOW_MODE_X11_MENU) { + _NET_WM_WINDOW_TYPE = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); + _NET_WM_WINDOW_TYPE_DROPDOWN_MENU = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", False); + X11_XChangeProperty(display, data->window, _NET_WM_WINDOW_TYPE, XA_ATOM, 32, + PropModeReplace, + (unsigned char *)&_NET_WM_WINDOW_TYPE_DROPDOWN_MENU, 1); + } else if (data->mode == SDL_TOOLKIT_WINDOW_MODE_X11_TOOLTIP) { + _NET_WM_WINDOW_TYPE = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); + _NET_WM_WINDOW_TYPE_TOOLTIP = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE_TOOLTIP", False); + X11_XChangeProperty(display, data->window, _NET_WM_WINDOW_TYPE, XA_ATOM, 32, + PropModeReplace, + (unsigned char *)&_NET_WM_WINDOW_TYPE_TOOLTIP, 1); + } + + // Allow the window to be deleted by the window manager + data->wm_delete_message = X11_XInternAtom(display, "WM_DELETE_WINDOW", False); + X11_XSetWMProtocols(display, data->window, &data->wm_delete_message, 1); + data->wm_protocols = X11_XInternAtom(display, "WM_PROTOCOLS", False); + + if (data->mode == SDL_TOOLKIT_WINDOW_MODE_X11_MENU || data->mode== SDL_TOOLKIT_WINDOW_MODE_X11_TOOLTIP) { + x = cx; + y = cy; + goto MOVEWINDOW; + } + if (windowdata) { + XWindowAttributes attrib; + Window dummy; + + X11_XGetWindowAttributes(display, windowdata->xwindow, &attrib); + x = attrib.x + (attrib.width - data->window_width) / 2; + y = attrib.y + (attrib.height - data->window_height) / 3; + X11_XTranslateCoordinates(display, windowdata->xwindow, RootWindow(display, data->screen), x, y, &x, &y, &dummy); + } else { + const SDL_VideoDevice *dev = SDL_GetVideoDevice(); + if (dev && dev->displays && dev->num_displays > 0) { + const SDL_VideoDisplay *dpy = dev->displays[0]; + const SDL_DisplayData *dpydata = dpy->internal; + x = dpydata->x + ((dpy->current_mode->w - data->window_width) / 2); + y = dpydata->y + ((dpy->current_mode->h - data->window_height) / 3); + } +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + else if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, use_xrandr_by_default) && data->xrandr) { + XRRScreenResources *screen_res; + XRRCrtcInfo *crtc_info; + RROutput default_out; + + screen_res = X11_XRRGetScreenResourcesCurrent(display, root_win); + if (!screen_res) { + goto NOXRANDR; + } + + default_out = X11_XRRGetOutputPrimary(display, root_win); + if (default_out != None) { + XRROutputInfo *out_info; + + out_info = X11_XRRGetOutputInfo(display, screen_res, default_out); + if (out_info->connection != RR_Connected) { + X11_XRRFreeOutputInfo(out_info); + goto FIRSTOUTPUTXRANDR; + } + + crtc_info = X11_XRRGetCrtcInfo(display, screen_res, out_info->crtc); + if (crtc_info) { + x = (crtc_info->width - data->window_width) / 2; + y = (crtc_info->height - data->window_height) / 3; + X11_XRRFreeOutputInfo(out_info); + X11_XRRFreeCrtcInfo(crtc_info); + X11_XRRFreeScreenResources(screen_res); + } else { + X11_XRRFreeOutputInfo(out_info); + goto NOXRANDR; + } + } else { + FIRSTOUTPUTXRANDR: + if (screen_res->noutput > 0) { + XRROutputInfo *out_info; + + out_info = X11_XRRGetOutputInfo(display, screen_res, screen_res->outputs[0]); + if (!out_info) { + goto FIRSTCRTCXRANDR; + } + + crtc_info = X11_XRRGetCrtcInfo(display, screen_res, out_info->crtc); + if (!crtc_info) { + X11_XRRFreeOutputInfo(out_info); + goto FIRSTCRTCXRANDR; + } + + x = (crtc_info->width - data->window_width) / 2; + y = (crtc_info->height - data->window_height) / 3; + X11_XRRFreeOutputInfo(out_info); + X11_XRRFreeCrtcInfo(crtc_info); + X11_XRRFreeScreenResources(screen_res); + goto MOVEWINDOW; + } + + FIRSTCRTCXRANDR: + if (!screen_res->ncrtc) { + X11_XRRFreeScreenResources(screen_res); + goto NOXRANDR; + } + + crtc_info = X11_XRRGetCrtcInfo(display, screen_res, screen_res->crtcs[0]); + if (crtc_info) { + x = (crtc_info->width - data->window_width) / 2; + y = (crtc_info->height - data->window_height) / 3; + X11_XRRFreeCrtcInfo(crtc_info); + X11_XRRFreeScreenResources(screen_res); + } else { + X11_XRRFreeScreenResources(screen_res); + goto NOXRANDR; + } + } + } +#endif + else { + // oh well. This will misposition on a multi-head setup. Init first next time. + NOXRANDR: + x = (DisplayWidth(display, data->screen) - data->window_width) / 2; + y = (DisplayHeight(display, data->screen) - data->window_height) / 3; + } + } + MOVEWINDOW: + X11_XMoveWindow(display, data->window, x, y); + data->window_x = x; + data->window_y = y; + + sizehints = X11_XAllocSizeHints(); + if (sizehints) { + sizehints->flags = USPosition | USSize | PMaxSize | PMinSize; + sizehints->x = x; + sizehints->y = y; + sizehints->width = data->window_width; + sizehints->height = data->window_height; + + sizehints->min_width = sizehints->max_width = data->window_width; + sizehints->min_height = sizehints->max_height = data->window_height; + + X11_XSetWMNormalHints(display, data->window, sizehints); + + X11_XFree(sizehints); + } + + X11_XMapRaised(display, data->window); + + data->drawable = data->window; + +#ifdef SDL_VIDEO_DRIVER_X11_XDBE + // Initialise a back buffer for double buffering + if (SDL_X11_HAVE_XDBE && !data->pixmap) { + int xdbe_major, xdbe_minor; + if (X11_XdbeQueryExtension(display, &xdbe_major, &xdbe_minor) != 0) { + data->xdbe = true; + data->buf = X11_XdbeAllocateBackBufferName(display, data->window, XdbeUndefined); + data->drawable = data->buf; + } else { + data->xdbe = false; + } + } +#endif + + if (data->pixmap) { + data->drawable = X11_XCreatePixmap(display, data->window, data->pixmap_width, data->pixmap_height, data->depth); + } + + SDL_zero(ctx_vals); + ctx_vals.foreground = data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].pixel; + ctx_vals.background = data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].pixel; + if (!data->utf8) { + gcflags |= GCFont; + ctx_vals.font = data->font_struct->fid; + } + data->ctx = X11_XCreateGC(data->display, data->drawable, gcflags, &ctx_vals); + if (data->ctx == None) { + return SDL_SetError("Couldn't create graphics context"); + } + + data->close = false; + data->key_control_esc = data->key_control_enter = NULL; + if (!data->pixmap) { + data->ev_scale = data->ev_iscale = 1; + } else { + data->ev_scale = data->scale; + data->ev_iscale = data->iscale; + } + +#if SDL_GRAB + if (data->mode == SDL_TOOLKIT_WINDOW_MODE_X11_MENU || data->mode== SDL_TOOLKIT_WINDOW_MODE_X11_TOOLTIP) { + X11_XGrabPointer(display, data->window, False, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); + X11_XGrabKeyboard(display, data->window, False, GrabModeAsync, GrabModeAsync, CurrentTime); + } +#endif + + return true; +} + +static void X11Toolkit_DrawWindow(SDL_ToolkitWindowX11 *data) { + int i; + +#ifdef SDL_VIDEO_DRIVER_X11_XDBE + if (SDL_X11_HAVE_XDBE && data->xdbe && !data->pixmap) { + X11_XdbeBeginIdiom(data->display); + } +#endif + + X11_XSetForeground(data->display, data->ctx, data->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].pixel); + if (data->pixmap) { + X11_XFillRectangle(data->display, data->drawable, data->ctx, 0, 0, data->pixmap_width, data->pixmap_height); + } else { + X11_XFillRectangle(data->display, data->drawable, data->ctx, 0, 0, data->window_width, data->window_height); + } + + for (i = 0; i < data->controls_sz; i++) { + SDL_ToolkitControlX11 *control; + + control = data->controls[i]; + if (control) { + if (control->func_draw) { + control->func_draw(control); + } + } + } + +#ifdef SDL_VIDEO_DRIVER_X11_XDBE + if (SDL_X11_HAVE_XDBE && data->xdbe && !data->pixmap) { + XdbeSwapInfo swap_info; + swap_info.swap_window = data->window; + swap_info.swap_action = XdbeUndefined; + X11_XdbeSwapBuffers(data->display, &swap_info, 1); + X11_XdbeEndIdiom(data->display); + } +#endif + + if (data->pixmap) { + XImage *pixmap_image; + XImage *put_image; + SDL_Surface *pixmap_surface; + SDL_Surface *put_surface; + + /* FIXME: Implement SHM transport? */ + pixmap_image = X11_XGetImage(data->display, data->drawable, 0, 0 , data->pixmap_width, data->pixmap_height, AllPlanes, ZPixmap); + pixmap_surface = SDL_CreateSurfaceFrom(data->pixmap_width, data->pixmap_height, X11_GetPixelFormatFromVisualInfo(data->display, &data->vi), pixmap_image->data, pixmap_image->bytes_per_line); + put_surface = SDL_ScaleSurface(pixmap_surface, data->window_width, data->window_height, SDL_SCALEMODE_LINEAR); + put_image = X11_XCreateImage(data->display, data->visual, data->vi.depth, ZPixmap, 0, put_surface->pixels, data->window_width, data->window_height, 32, put_surface->pitch); + X11_XPutImage(data->display, data->window, data->ctx, put_image, 0, 0, 0, 0, data->window_width, data->window_height); + + XDestroyImage(pixmap_image); + /* Needed because XDestroyImage results in a double-free otherwise */ + put_image->data = NULL; + XDestroyImage(put_image); + SDL_DestroySurface(pixmap_surface); + SDL_DestroySurface(put_surface); + } + + X11_XFlush(data->display); +} + +static SDL_ToolkitControlX11 *X11Toolkit_GetControlMouseIsOn(SDL_ToolkitWindowX11 *data, int x, int y) +{ + int i; + + for (i = 0; i < data->controls_sz; i++) { + SDL_Rect *rect = &data->controls[i]->rect; + if ((x >= rect->x) && + (x <= (rect->x + rect->w)) && + (y >= rect->y) && + (y <= (rect->y + rect->h))) { + return data->controls[i]; + } + } + + return NULL; +} + +// NOLINTNEXTLINE(readability-non-const-parameter): cannot make XPointer a const pointer due to typedef +static Bool X11Toolkit_EventTest(Display *display, XEvent *event, XPointer arg) +{ + SDL_ToolkitWindowX11 *data = (SDL_ToolkitWindowX11 *)arg; + + if (event->xany.display != data->display) { + return False; + } + + if (event->xany.window == data->window) { + return True; + } + + return False; +} + +void X11Toolkit_ProcessWindowEvents(SDL_ToolkitWindowX11 *data, XEvent *e) { + /* If X11_XFilterEvent returns True, then some input method has filtered the + event, and the client should discard the event. */ + if ((e->type != Expose) && X11_XFilterEvent(e, None)) { + return; + } + + data->draw = false; + data->e = e; + + switch (e->type) { + case Expose: + data->draw = true; + break; + case ClientMessage: + if (e->xclient.message_type == data->wm_protocols && + e->xclient.format == 32 && + e->xclient.data.l[0] == data->wm_delete_message) { + data->close = true; + } + break; + case FocusIn: + data->has_focus = true; + break; + case FocusOut: + data->has_focus = false; + if (data->fiddled_control) { + data->fiddled_control->selected = false; + } + data->fiddled_control = NULL; + for (data->ev_i = 0; data->ev_i < data->controls_sz; data->ev_i++) { + data->controls[data->ev_i]->state = SDL_TOOLKIT_CONTROL_STATE_X11_NORMAL; + } + break; + case MotionNotify: + if (data->has_focus) { + data->previous_control = data->fiddled_control; + data->fiddled_control = X11Toolkit_GetControlMouseIsOn(data, SDL_lroundf((e->xbutton.x/ data->ev_scale)* data->ev_iscale), SDL_lroundf((e->xbutton.y/ data->ev_scale)* data->ev_iscale)); + if (data->previous_control) { + data->previous_control->state = SDL_TOOLKIT_CONTROL_STATE_X11_NORMAL; + if (data->previous_control->func_on_state_change) { + data->previous_control->func_on_state_change(data->previous_control); + } + data->draw = true; + } + if (data->fiddled_control) { + if (data->fiddled_control->dynamic) { + data->fiddled_control->state = SDL_TOOLKIT_CONTROL_STATE_X11_HOVER; + if (data->fiddled_control->func_on_state_change) { + data->fiddled_control->func_on_state_change(data->fiddled_control); + } + data->draw = true; + } else { + data->fiddled_control = NULL; + } + } + } + break; + case ButtonPress: + data->previous_control = data->fiddled_control; + if (data->previous_control) { + data->previous_control->state = SDL_TOOLKIT_CONTROL_STATE_X11_NORMAL; + if (data->previous_control->func_on_state_change) { + data->previous_control->func_on_state_change(data->previous_control); + } + data->draw = true; + } + if (e->xbutton.button == Button1) { + data->fiddled_control = X11Toolkit_GetControlMouseIsOn(data, SDL_lroundf((e->xbutton.x/ data->ev_scale)* data->ev_iscale), SDL_lroundf((e->xbutton.y/ data->ev_scale)* data->ev_iscale)); + if (data->fiddled_control) { + data->fiddled_control->state = SDL_TOOLKIT_CONTROL_STATE_X11_PRESSED_HELD; + if (data->fiddled_control->func_on_state_change) { + data->fiddled_control->func_on_state_change(data->fiddled_control); + } + data->draw = true; + } + } + break; + case ButtonRelease: + if (data->mode == SDL_TOOLKIT_WINDOW_MODE_X11_MENU || data->mode== SDL_TOOLKIT_WINDOW_MODE_X11_TOOLTIP) { + int cx; + int cy; + + cx = e->xbutton.x; + cy = e->xbutton.y; + + if (cy < 0 || cx < 0) { + data->close = true; + } + + if (cy > data->window_height || cx > data->window_width) { + data->close = true; + } + } + + if ((e->xbutton.button == Button1) && (data->fiddled_control)) { + SDL_ToolkitControlX11 *control; + + control = X11Toolkit_GetControlMouseIsOn(data, SDL_lroundf((e->xbutton.x/ data->ev_scale)* data->ev_iscale), SDL_lroundf((e->xbutton.y/ data->ev_scale)* data->ev_iscale)); + if (data->fiddled_control == control) { + data->fiddled_control->state = SDL_TOOLKIT_CONTROL_STATE_X11_PRESSED; + if (data->fiddled_control->func_on_state_change) { + data->fiddled_control->func_on_state_change(data->fiddled_control); + } + data->fiddled_control->state = SDL_TOOLKIT_CONTROL_STATE_X11_NORMAL; + data->draw = true; + } + } + break; + case KeyPress: + data->last_key_pressed = X11_XLookupKeysym(&e->xkey, 0); + + if (data->last_key_pressed == XK_Escape) { + for (data->ev_i = 0; data->ev_i < data->controls_sz; data->ev_i++) { + if(data->controls[data->ev_i]->is_default_esc) { + data->controls[data->ev_i]->state = SDL_TOOLKIT_CONTROL_STATE_X11_PRESSED; + data->draw = true; + data->key_control_esc = data->controls[data->ev_i]; + } + } + } else if ((data->last_key_pressed == XK_Return) || (data->last_key_pressed == XK_KP_Enter)) { + for (data->ev_i = 0; data->ev_i < data->controls_sz; data->ev_i++) { + if(data->controls[data->ev_i]->selected) { + data->controls[data->ev_i]->state = SDL_TOOLKIT_CONTROL_STATE_X11_PRESSED; + data->draw = true; + data->key_control_enter = data->controls[data->ev_i]; + } + } + } + break; + case KeyRelease: + { + KeySym key = X11_XLookupKeysym(&e->xkey, 0); + + // If this is a key release for something we didn't get the key down for, then bail. + if (key != data->last_key_pressed) { + break; + } + + if (key == XK_Escape) { + if (data->key_control_esc) { + if (data->key_control_esc->func_on_state_change) { + data->key_control_esc->func_on_state_change(data->key_control_esc); + } + } + } else if ((key == XK_Return) || (key == XK_KP_Enter)) { + if (data->key_control_enter) { + if (data->key_control_enter->func_on_state_change) { + data->key_control_enter->func_on_state_change(data->key_control_enter); + } + } + } else if (key == XK_Tab || key == XK_Left || key == XK_Right) { + if (data->focused_control) { + data->focused_control->selected = false; + } + data->draw = true; + for (data->ev_i = 0; data->ev_i < data->dyn_controls_sz; data->ev_i++) { + if (data->dyn_controls[data->ev_i] == data->focused_control) { + int next_index; + + if (key == XK_Left) { + next_index = data->ev_i - 1; + } else { + next_index = data->ev_i + 1; + } + if ((next_index >= data->dyn_controls_sz) || (next_index < 0)) { + if (key == XK_Right || key == XK_Left) { + next_index = data->ev_i; + } else { + next_index = 0; + } + } + data->focused_control = data->dyn_controls[next_index]; + data->focused_control->selected = true; + break; + } + } + } + break; + } + } + + if (data->draw) { + X11Toolkit_DrawWindow(data); + } +} + +void X11Toolkit_DoWindowEventLoop(SDL_ToolkitWindowX11 *data) { + while (!data->close) { + XEvent e; + + /* Process settings events */ + X11_XPeekEvent(data->display, &e); + if (data->xsettings) { + xsettings_client_process_event(data->xsettings, &e); + } + + /* Do actual event loop */ + X11_XIfEvent(data->display, &e, X11Toolkit_EventTest, (XPointer)data); + X11Toolkit_ProcessWindowEvents(data, &e); + } +} + + +void X11Toolkit_ResizeWindow(SDL_ToolkitWindowX11 *data, int w, int h) { + if (!data->pixmap) { + data->window_width = w; + data->window_height = h; + } else { + data->window_width = SDL_lroundf((w/data->iscale) * data->scale); + data->window_height = SDL_lroundf((h/data->iscale) * data->scale); + data->pixmap_width = w; + data->pixmap_height = h; + X11_XFreePixmap(data->display, data->drawable); + data->drawable = X11_XCreatePixmap(data->display, data->window, data->pixmap_width, data->pixmap_height, data->depth); + } + + X11_XResizeWindow(data->display, data->window, data->window_width, data->window_height); +} + +static void X11Toolkit_DestroyIconControl(SDL_ToolkitControlX11 *control) { + SDL_ToolkitIconControlX11 *icon_control; + + icon_control = (SDL_ToolkitIconControlX11 *)control; + X11_XFreeFont(control->window->display, icon_control->icon_char_font); + SDL_free(control); +} + +static void X11Toolkit_DrawIconControl(SDL_ToolkitControlX11 *control) { + SDL_ToolkitIconControlX11 *icon_control; + + icon_control = (SDL_ToolkitIconControlX11 *)control; + control->rect.w -= 2 * control->window->iscale; + control->rect.h -= 2 * control->window->iscale; + X11_XSetForeground(control->window->display, control->window->ctx, icon_control->xcolor_bg_shadow.pixel); + X11_XFillArc(control->window->display, control->window->drawable, control->window->ctx, control->rect.x + (2 * control->window->iscale), control->rect.y + (2* control->window->iscale), control->rect.w, control->rect.h, 0, 360 * 64); + + switch (icon_control->flags & (SDL_MESSAGEBOX_ERROR | SDL_MESSAGEBOX_WARNING | SDL_MESSAGEBOX_INFORMATION)) { + case SDL_MESSAGEBOX_ERROR: + X11_XSetForeground(control->window->display, control->window->ctx, icon_control->xcolor_red_darker.pixel); + X11_XFillArc(control->window->display, control->window->drawable, control->window->ctx, control->rect.x, control->rect.y, control->rect.w, control->rect.h, 0, 360 * 64); + X11_XSetForeground(control->window->display, control->window->ctx, icon_control->xcolor_red.pixel); + X11_XFillArc(control->window->display, control->window->drawable, control->window->ctx, control->rect.x+(1* control->window->iscale), control->rect.y+(1* control->window->iscale), control->rect.w-(2* control->window->iscale), control->rect.h-(2* control->window->iscale), 0, 360 * 64); + X11_XSetForeground(control->window->display, control->window->ctx, icon_control->xcolor_white.pixel); + break; + case SDL_MESSAGEBOX_WARNING: + X11_XSetForeground(control->window->display, control->window->ctx, icon_control->xcolor_black.pixel); + X11_XFillArc(control->window->display, control->window->drawable, control->window->ctx, control->rect.x, control->rect.y, control->rect.w, control->rect.h, 0, 360 * 64); + X11_XSetForeground(control->window->display, control->window->ctx, icon_control->xcolor_yellow.pixel); + X11_XFillArc(control->window->display, control->window->drawable, control->window->ctx, control->rect.x+(1* control->window->iscale), control->rect.y+(1* control->window->iscale), control->rect.w-(2* control->window->iscale), control->rect.h-(2* control->window->iscale), 0, 360 * 64); + X11_XSetForeground(control->window->display, control->window->ctx, icon_control->xcolor_black.pixel); + break; + case SDL_MESSAGEBOX_INFORMATION: + X11_XSetForeground(control->window->display, control->window->ctx, icon_control->xcolor_white.pixel); + X11_XFillArc(control->window->display, control->window->drawable, control->window->ctx, control->rect.x, control->rect.y, control->rect.w, control->rect.h, 0, 360 * 64); + X11_XSetForeground(control->window->display, control->window->ctx, icon_control->xcolor_blue.pixel); + X11_XFillArc(control->window->display, control->window->drawable, control->window->ctx, control->rect.x+(1* control->window->iscale), control->rect.y+(1* control->window->iscale), control->rect.w-(2* control->window->iscale), control->rect.h-(2* control->window->iscale), 0, 360 * 64); + X11_XSetForeground(control->window->display, control->window->ctx, icon_control->xcolor_white.pixel); + break; + } + X11_XSetFont(control->window->display, control->window->ctx, icon_control->icon_char_font->fid); + X11_XDrawString(control->window->display, control->window->drawable, control->window->ctx, control->rect.x + icon_control->icon_char_x, control->rect.y + icon_control->icon_char_y, &icon_control->icon_char, 1); + if (!control->window->utf8) { + X11_XSetFont(control->window->display, control->window->ctx, control->window->font_struct->fid); + } + + control->rect.w += 2 * control->window->iscale; + control->rect.h += 2 * control->window->iscale; +} + +static void X11Toolkit_CalculateIconControl(SDL_ToolkitControlX11 *base_control) { + SDL_ToolkitIconControlX11 *control; + int icon_char_w; + int icon_char_h; + int icon_char_max; + + control = (SDL_ToolkitIconControlX11 *)base_control; + X11Toolkit_GetTextWidthHeightForFont(control->icon_char_font, &control->icon_char, 1, &icon_char_w, &icon_char_h, &control->icon_char_a); + base_control->rect.w = icon_char_w + SDL_TOOLKIT_X11_ELEMENT_PADDING * 2 * base_control->window->iscale; + base_control->rect.h = icon_char_h + SDL_TOOLKIT_X11_ELEMENT_PADDING * 2 * base_control->window->iscale; + icon_char_max = SDL_max(base_control->rect.w, base_control->rect.h) + 2; + base_control->rect.w = icon_char_max; + base_control->rect.h = icon_char_max; + base_control->rect.y = 0; + base_control->rect.x = 0; + control->icon_char_y = control->icon_char_a + (base_control->rect.h - icon_char_h)/2 + 1; + control->icon_char_x = (base_control->rect.w - icon_char_w)/2 + 1; + base_control->rect.w += 2 * base_control->window->iscale; + base_control->rect.h += 2 * base_control->window->iscale; +} + +static void X11Toolkit_OnIconControlScaleChange(SDL_ToolkitControlX11 *base_control) { + SDL_ToolkitIconControlX11 *control; + char *font; + + control = (SDL_ToolkitIconControlX11 *)base_control; + X11_XFreeFont(base_control->window->display, control->icon_char_font); + SDL_asprintf(&font, g_IconFont, G_ICONFONT_SIZE * base_control->window->iscale); + control->icon_char_font = X11_XLoadQueryFont(base_control->window->display, font); + SDL_free(font); + if (!control->icon_char_font) { + SDL_asprintf(&font, g_ToolkitFontLatin1, G_TOOLKITFONT_SIZE * base_control->window->iscale); + control->icon_char_font = X11_XLoadQueryFont(base_control->window->display, font); + SDL_free(font); + } +} + +SDL_ToolkitControlX11 *X11Toolkit_CreateIconControl(SDL_ToolkitWindowX11 *window, SDL_MessageBoxFlags flags) { + SDL_ToolkitIconControlX11 *control; + SDL_ToolkitControlX11 *base_control; + char *font; + + /* Create control struct */ + control = (SDL_ToolkitIconControlX11 *)SDL_malloc(sizeof(SDL_ToolkitIconControlX11)); + base_control = (SDL_ToolkitControlX11 *)control; + if (!control) { + SDL_SetError("Unable to allocate icon control structure"); + return NULL; + } + + /* Fill out struct */ + base_control->window = window; + base_control->func_draw = X11Toolkit_DrawIconControl; + base_control->func_free = X11Toolkit_DestroyIconControl; + base_control->func_on_state_change = NULL; + base_control->func_calc_size = X11Toolkit_CalculateIconControl; + base_control->func_on_scale_change = X11Toolkit_OnIconControlScaleChange; + base_control->state = SDL_TOOLKIT_CONTROL_STATE_X11_NORMAL; + base_control->selected = false; + base_control->dynamic = false; + base_control->is_default_enter = false; + base_control->is_default_esc = false; + control->flags = flags; + + /* Load font */ + SDL_asprintf(&font, g_IconFont, G_ICONFONT_SIZE * window->iscale); + control->icon_char_font = X11_XLoadQueryFont(window->display, font); + SDL_free(font); + if (!control->icon_char_font) { + SDL_asprintf(&font, g_ToolkitFontLatin1, G_TOOLKITFONT_SIZE * window->iscale); + control->icon_char_font = X11_XLoadQueryFont(window->display, font); + SDL_free(font); + if (!control->icon_char_font) { + SDL_free(control); + return NULL; + } + } + + /* Set colors */ + switch (flags & (SDL_MESSAGEBOX_ERROR | SDL_MESSAGEBOX_WARNING | SDL_MESSAGEBOX_INFORMATION)) { + case SDL_MESSAGEBOX_ERROR: + control->icon_char = 'X'; + control->xcolor_white.flags = DoRed|DoGreen|DoBlue; + control->xcolor_white.red = 65535; + control->xcolor_white.green = 65535; + control->xcolor_white.blue = 65535; + control->xcolor_red.flags = DoRed|DoGreen|DoBlue; + control->xcolor_red.red = 65535; + control->xcolor_red.green = 0; + control->xcolor_red.blue = 0; + control->xcolor_red_darker.flags = DoRed|DoGreen|DoBlue; + control->xcolor_red_darker.red = 40535; + control->xcolor_red_darker.green = 0; + control->xcolor_red_darker.blue = 0; + X11_XAllocColor(window->display, window->cmap, &control->xcolor_white); + X11_XAllocColor(window->display, window->cmap, &control->xcolor_red); + X11_XAllocColor(window->display, window->cmap, &control->xcolor_red_darker); + break; + case SDL_MESSAGEBOX_WARNING: + control->icon_char = '!'; + control->xcolor_black.flags = DoRed|DoGreen|DoBlue; + control->xcolor_black.red = 0; + control->xcolor_black.green = 0; + control->xcolor_black.blue = 0; + control->xcolor_yellow.flags = DoRed|DoGreen|DoBlue; + control->xcolor_yellow.red = 65535; + control->xcolor_yellow.green = 65535; + control->xcolor_yellow.blue = 0; + X11_XAllocColor(window->display, window->cmap, &control->xcolor_black); + X11_XAllocColor(window->display, window->cmap, &control->xcolor_yellow); + break; + case SDL_MESSAGEBOX_INFORMATION: + control->icon_char = 'i'; + control->xcolor_white.flags = DoRed|DoGreen|DoBlue; + control->xcolor_white.red = 65535; + control->xcolor_white.green = 65535; + control->xcolor_white.blue = 65535; + control->xcolor_blue.flags = DoRed|DoGreen|DoBlue; + control->xcolor_blue.red = 0; + control->xcolor_blue.green = 0; + control->xcolor_blue.blue = 65535; + X11_XAllocColor(window->display, window->cmap, &control->xcolor_white); + X11_XAllocColor(window->display, window->cmap, &control->xcolor_blue); + break; + default: + X11_XFreeFont(window->display, control->icon_char_font); + SDL_free(control); + return NULL; + } + control->xcolor_bg_shadow.flags = DoRed|DoGreen|DoBlue; + control->xcolor_bg_shadow.red = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].red - 12500, 0, 65535); + control->xcolor_bg_shadow.green = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].green - 12500, 0, 65535); + control->xcolor_bg_shadow.blue = SDL_clamp(window->xcolor[SDL_MESSAGEBOX_COLOR_BACKGROUND].blue - 12500, 0, 65535); + X11_XAllocColor(window->display, window->cmap, &control->xcolor_bg_shadow); + + /* Sizing and positioning */ + X11Toolkit_CalculateIconControl(base_control); + + X11Toolkit_AddControlToWindow(window, base_control); + return base_control; +} + +bool X11Toolkit_NotifyControlOfSizeChange(SDL_ToolkitControlX11 *control) { + if (control->func_calc_size) { + control->func_calc_size(control); + return true; + } else { + return false; + } +} + +static void X11Toolkit_CalculateButtonControl(SDL_ToolkitControlX11 *control) { + SDL_ToolkitButtonControlX11 *button_control; + int text_d; + + button_control = (SDL_ToolkitButtonControlX11 *)control; + X11Toolkit_GetTextWidthHeight(control->window, button_control->data->text, button_control->str_sz, &button_control->text_rect.w, &button_control->text_rect.h, &button_control->text_a, &text_d); + //control->rect.w = SDL_TOOLKIT_X11_ELEMENT_PADDING_3 * 2 * control->window->iscale + button_control->text_rect.w; + //control->rect.h = SDL_TOOLKIT_X11_ELEMENT_PADDING_3 * 2 * control->window->iscale + button_control->text_rect.h; + button_control->text_rect.x = (control->rect.w - button_control->text_rect.w)/2; + button_control->text_rect.y = button_control->text_a + (control->rect.h - button_control->text_rect.h)/2; + if (control->window->utf8) { + button_control->text_rect.y -= 2 * control->window->iscale; + } else { + button_control->text_rect.y -= 4 * control->window->iscale; + } +} + + +static void X11Toolkit_DrawButtonControl(SDL_ToolkitControlX11 *control) { + SDL_ToolkitButtonControlX11 *button_control; + + button_control = (SDL_ToolkitButtonControlX11 *)control; + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].pixel); + /* Draw bevel */ + if (control->state == SDL_TOOLKIT_CONTROL_STATE_X11_PRESSED || control->state == SDL_TOOLKIT_CONTROL_STATE_X11_PRESSED_HELD) { + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor_bevel_d.pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x, control->rect.y, + control->rect.w, control->rect.h); + + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor_bevel_l2.pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x, control->rect.y, + control->rect.w - (1* control->window->iscale), control->rect.h - (1* control->window->iscale)); + + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor_bevel_l1.pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x + 1 * control->window->iscale, control->rect.y + 1 * control->window->iscale, + control->rect.w - 3 * control->window->iscale, control->rect.h - 2 * control->window->iscale); + + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x + 1 * control->window->iscale, control->rect.y + 1 * control->window->iscale, + control->rect.w - 3 * control->window->iscale, control->rect.h - 3 * control->window->iscale); + + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor_pressed.pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x + 2 * control->window->iscale, control->rect.y + 2 * control->window->iscale, + control->rect.w - 4 * control->window->iscale, control->rect.h - 4 * control->window->iscale); + } else { + if (control->selected) { + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor_bevel_d.pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x, control->rect.y, + control->rect.w, control->rect.h); + + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor_bevel_l2.pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x + 1 * control->window->iscale, control->rect.y + 1 * control->window->iscale, + control->rect.w - 3 * control->window->iscale, control->rect.h - 3 * control->window->iscale); + + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x + 2 * control->window->iscale, control->rect.y + 2 * control->window->iscale, + control->rect.w - 4 * control->window->iscale, control->rect.h - 4 * control->window->iscale); + + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor_bevel_l1.pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x + 2 * control->window->iscale, control->rect.y + 2 * control->window->iscale, + control->rect.w - 5 * control->window->iscale, control->rect.h - 5 * control->window->iscale); + + X11_XSetForeground(control->window->display, control->window->ctx, (control->state == SDL_TOOLKIT_CONTROL_STATE_X11_HOVER) ? control->window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED].pixel : control->window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND].pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x + 3 * control->window->iscale, control->rect.y + 3 * control->window->iscale, + control->rect.w - 6 * control->window->iscale, control->rect.h - 6 * control->window->iscale); + } else { + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor_bevel_d.pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x, control->rect.y, + control->rect.w, control->rect.h); + + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor_bevel_l2.pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x, control->rect.y, + control->rect.w - 1 * control->window->iscale, control->rect.h - 1 * control->window->iscale); + + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER].pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x + 1 * control->window->iscale, control->rect.y + 1 * control->window->iscale, + control->rect.w - 2 * control->window->iscale, control->rect.h - 2 * control->window->iscale); + + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor_bevel_l1.pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x + 1 * control->window->iscale, control->rect.y + 1 * control->window->iscale, + control->rect.w - 3 * control->window->iscale, control->rect.h - 3 * control->window->iscale); + + X11_XSetForeground(control->window->display, control->window->ctx, (control->state == SDL_TOOLKIT_CONTROL_STATE_X11_HOVER) ? control->window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED].pixel : control->window->xcolor[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND].pixel); + X11_XFillRectangle(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x + 2 * control->window->iscale, control->rect.y + 2 * control->window->iscale, + control->rect.w - 4 * control->window->iscale, control->rect.h - 4 * control->window->iscale); + } + } + + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].pixel); +#ifdef X_HAVE_UTF8_STRING + if (control->window->utf8) { + X11_Xutf8DrawString(control->window->display, control->window->drawable, control->window->font_set, control->window->ctx, + control->rect.x + button_control->text_rect.x, + control->rect.y + button_control->text_rect.y, + button_control->data->text, button_control->str_sz); + } else +#endif + { + X11_XDrawString(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x + button_control->text_rect.x, control->rect.y + button_control->text_rect.y, + button_control->data->text, button_control->str_sz); + } +} + +static void X11Toolkit_OnButtonControlStateChange(SDL_ToolkitControlX11 *control) { + SDL_ToolkitButtonControlX11 *button_control; + + button_control = (SDL_ToolkitButtonControlX11 *)control; + if (button_control->cb && control->state == SDL_TOOLKIT_CONTROL_STATE_X11_PRESSED) { + button_control->cb(control, button_control->cb_data); + } +} + +static void X11Toolkit_DestroyGenericControl(SDL_ToolkitControlX11 *control) { + SDL_free(control); +} + +SDL_ToolkitControlX11 *X11Toolkit_CreateButtonControl(SDL_ToolkitWindowX11 *window, const SDL_MessageBoxButtonData *data) { + SDL_ToolkitButtonControlX11 *control; + SDL_ToolkitControlX11 *base_control; + int text_d; + + control = (SDL_ToolkitButtonControlX11 *)SDL_malloc(sizeof(SDL_ToolkitButtonControlX11)); + base_control = (SDL_ToolkitControlX11 *)control; + if (!control) { + SDL_SetError("Unable to allocate button control structure"); + return NULL; + } + base_control->window = window; + base_control->state = SDL_TOOLKIT_CONTROL_STATE_X11_NORMAL; + base_control->func_calc_size = X11Toolkit_CalculateButtonControl; + base_control->func_draw = X11Toolkit_DrawButtonControl; + base_control->func_on_state_change = X11Toolkit_OnButtonControlStateChange; + base_control->func_free = X11Toolkit_DestroyGenericControl; + base_control->func_on_scale_change = NULL; + base_control->state = SDL_TOOLKIT_CONTROL_STATE_X11_NORMAL; + base_control->selected = false; + base_control->dynamic = true; + base_control->is_default_enter = false; + base_control->is_default_esc = false; + if (data->flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT) { + base_control->is_default_esc = true; + } + if (data->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) { + base_control->is_default_enter = true; + base_control->selected = true; + } + control->data = data; + control->str_sz = SDL_strlen(control->data->text); + control->cb = NULL; + X11Toolkit_GetTextWidthHeight(base_control->window, control->data->text, control->str_sz, &control->text_rect.w, &control->text_rect.h, &control->text_a, &text_d); + base_control->rect.w = SDL_TOOLKIT_X11_ELEMENT_PADDING_3 * 2 * window->iscale + control->text_rect.w; + base_control->rect.h = SDL_TOOLKIT_X11_ELEMENT_PADDING_3 * 2 * window->iscale + control->text_rect.h; + control->text_rect.x = control->text_rect.y = 0; + X11Toolkit_CalculateButtonControl(base_control); + X11Toolkit_AddControlToWindow(window, base_control); + return base_control; +} + +void X11Toolkit_RegisterCallbackForButtonControl(SDL_ToolkitControlX11 *control, void *data, void (*cb)(struct SDL_ToolkitControlX11 *, void *)) { + SDL_ToolkitButtonControlX11 *button_control; + + button_control = (SDL_ToolkitButtonControlX11 *)control; + button_control->cb_data = data; + button_control->cb = cb; +} + +const SDL_MessageBoxButtonData *X11Toolkit_GetButtonControlData(SDL_ToolkitControlX11 *control) { + SDL_ToolkitButtonControlX11 *button_control; + + button_control = (SDL_ToolkitButtonControlX11 *)control; + return button_control->data; +} + +void X11Toolkit_DestroyWindow(SDL_ToolkitWindowX11 *data) { + int i; + + if (!data) { + return; + } + +#if SDL_GRAB + if (data->mode == SDL_TOOLKIT_WINDOW_MODE_X11_MENU || data->mode== SDL_TOOLKIT_WINDOW_MODE_X11_TOOLTIP) { + X11_XUngrabPointer(data->display, CurrentTime); + X11_XUngrabKeyboard(data->display, CurrentTime); + } +#endif + + for (i = 0; i < data->controls_sz; i++) { + if (data->controls[i]->func_free) { + data->controls[i]->func_free(data->controls[i]); + } + } + if (data->controls) { + SDL_free(data->controls); + } + if (data->dyn_controls) { + SDL_free(data->dyn_controls); + } + + if (data->popup_windows) { + SDL_ListClear(&data->popup_windows); + } + + if (data->pixmap) { + X11_XFreePixmap(data->display, data->drawable); + } + +#ifdef X_HAVE_UTF8_STRING + if (data->font_set) { + X11_XFreeFontSet(data->display, data->font_set); + data->font_set = NULL; + } +#endif + + if (data->font_struct) { + X11_XFreeFont(data->display, data->font_struct); + data->font_struct = NULL; + } + +#ifdef SDL_VIDEO_DRIVER_X11_XDBE + if (SDL_X11_HAVE_XDBE && data->xdbe && !data->pixmap) { + X11_XdbeDeallocateBackBufferName(data->display, data->buf); + } +#endif + + if (data->xsettings) { + xsettings_client_destroy(data->xsettings); + } + + X11_XFreeGC(data->display, data->ctx); + + if (data->display) { + if (data->window != None) { + X11_XWithdrawWindow(data->display, data->window, data->screen); + X11_XDestroyWindow(data->display, data->window); + data->window = None; + } + + if (data->display_close) { + X11_XCloseDisplay(data->display); + } + data->display = NULL; + } + +#if SDL_SET_LOCALE + if (data->origlocale && (data->mode == SDL_TOOLKIT_WINDOW_MODE_X11_DIALOG)) { + (void)setlocale(LC_ALL, data->origlocale); + SDL_free(data->origlocale); + } +#endif + + SDL_free(data); +} + +static int X11Toolkit_CountLinesOfText(const char *text) +{ + int result = 0; + while (text && *text) { + const char *lf = SDL_strchr(text, '\n'); + result++; // even without an endline, this counts as a line. + text = lf ? lf + 1 : NULL; + } + return result; +} + +static void X11Toolkit_DrawLabelControl(SDL_ToolkitControlX11 *control) { + SDL_ToolkitLabelControlX11 *label_control; + int i; + + label_control = (SDL_ToolkitLabelControlX11 *)control; + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].pixel); + for (i = 0; i < label_control->sz; i++) { +#ifdef X_HAVE_UTF8_STRING + if (control->window->utf8) { + X11_Xutf8DrawString(control->window->display, control->window->drawable, control->window->font_set, control->window->ctx, + control->rect.x, control->rect.y + label_control->y[i], + label_control->lines[i], label_control->szs[i]); + } else +#endif + { + X11_XDrawString(control->window->display, control->window->drawable, control->window->ctx, + control->rect.x, control->rect.y + label_control->y[i], + label_control->lines[i], label_control->szs[i]); + } + } +} + +static void X11Toolkit_DestroyLabelControl(SDL_ToolkitControlX11 *control) { + SDL_ToolkitLabelControlX11 *label_control; + + label_control = (SDL_ToolkitLabelControlX11 *)control; + SDL_free(label_control->lines); + SDL_free(label_control->szs); + SDL_free(label_control->y); + SDL_free(label_control); +} + + +static void X11Toolkit_CalculateLabelControl(SDL_ToolkitControlX11 *base_control) { + SDL_ToolkitLabelControlX11 *control; + int ascent; + int descent; + int w; + int h; + int i; + + control = (SDL_ToolkitLabelControlX11 *)base_control; + for (i = 0; i < control->sz; i++) { + X11Toolkit_GetTextWidthHeight(base_control->window, control->lines[i], control->szs[i], &w, &h, &ascent, &descent); + + if (i > 0) { + control->y[i] = ascent + descent + control->y[i-1]; + base_control->rect.h += ascent + descent + h; + } else { + control->y[i] = ascent; + base_control->rect.h = h; + } + } +} + +SDL_ToolkitControlX11 *X11Toolkit_CreateLabelControl(SDL_ToolkitWindowX11 *window, char *utf8) { + SDL_ToolkitLabelControlX11 *control; + SDL_ToolkitControlX11 *base_control; + int ascent; + int descent; + int i; + + if (!utf8) { + return NULL; + } + control = (SDL_ToolkitLabelControlX11 *)SDL_malloc(sizeof(SDL_ToolkitLabelControlX11)); + base_control = (SDL_ToolkitControlX11 *)control; + if (!control) { + SDL_SetError("Unable to allocate label control structure"); + return NULL; + } + base_control->window = window; + base_control->func_draw = X11Toolkit_DrawLabelControl; + base_control->func_on_state_change = NULL; + base_control->func_calc_size = X11Toolkit_CalculateLabelControl; + base_control->func_free = X11Toolkit_DestroyLabelControl; + base_control->func_on_scale_change = NULL; + base_control->state = SDL_TOOLKIT_CONTROL_STATE_X11_NORMAL; + base_control->selected = false; + base_control->dynamic = false; + base_control->rect.w = 0; + base_control->rect.h = 0; + base_control->is_default_enter = false; + base_control->is_default_esc = false; + control->sz = X11Toolkit_CountLinesOfText(utf8); + control->lines = (char **)SDL_malloc(sizeof(char *) * control->sz); + control->y = (int *)SDL_calloc(control->sz, sizeof(int)); + control->szs = (size_t *)SDL_calloc(control->sz, sizeof(size_t)); + for (i = 0; i < control->sz; i++) { + const char *lf = SDL_strchr(utf8, '\n'); + const int length = lf ? (lf - utf8) : SDL_strlen(utf8); + int w; + int h; + + control->lines[i] = utf8; + X11Toolkit_GetTextWidthHeight(window, utf8, length, &w, &h, &ascent, &descent); + base_control->rect.w = SDL_max(base_control->rect.w, w); + + control->szs[i] = length; + if (lf && (lf > utf8) && (lf[-1] == '\r')) { + control->szs[i]--; + } + + if (i > 0) { + control->y[i] = ascent + descent + control->y[i-1]; + base_control->rect.h += ascent + descent + h; + } else { + control->y[i] = ascent; + base_control->rect.h = h; + } + utf8 += length + 1; + + if (!lf) { + break; + } + } + + X11Toolkit_AddControlToWindow(window, base_control); + return base_control; +} + +int X11Toolkit_GetIconControlCharY(SDL_ToolkitControlX11 *control) { + SDL_ToolkitIconControlX11 *icon_control; + + icon_control = (SDL_ToolkitIconControlX11 *)control; + return icon_control->icon_char_y - icon_control->icon_char_a; +} + +void X11Toolkit_SignalWindowClose(SDL_ToolkitWindowX11 *data) { + data->close = true; +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/src/video/x11/SDL_x11toolkit.h b/src/video/x11/SDL_x11toolkit.h new file mode 100644 index 0000000000..5e0170c1da --- /dev/null +++ b/src/video/x11/SDL_x11toolkit.h @@ -0,0 +1,225 @@ +/* + 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_internal.h" + +#ifndef SDL_x11toolkit_h_ +#define SDL_x11toolkit_h_ + +#include "../../SDL_list.h" +#include "SDL_x11video.h" +#include "SDL_x11dyn.h" +#include "SDL_x11settings.h" +#include "SDL_x11toolkit.h" +#include "xsettings-client.h" + +#ifdef SDL_VIDEO_DRIVER_X11 + +/* Various predefined paddings */ +#define SDL_TOOLKIT_X11_ELEMENT_PADDING 4 +#define SDL_TOOLKIT_X11_ELEMENT_PADDING_2 12 +#define SDL_TOOLKIT_X11_ELEMENT_PADDING_3 8 +#define SDL_TOOLKIT_X11_ELEMENT_PADDING_4 16 +#define SDL_TOOLKIT_X11_ELEMENT_PADDING_5 3 + +typedef enum SDL_ToolkitChildModeX11 +{ + SDL_TOOLKIT_WINDOW_MODE_X11_DIALOG, + SDL_TOOLKIT_WINDOW_MODE_X11_CHILD, /* For embedding into a normal SDL_Window */ + SDL_TOOLKIT_WINDOW_MODE_X11_MENU, + SDL_TOOLKIT_WINDOW_MODE_X11_TOOLTIP +} SDL_ToolkitWindowModeX11; + +typedef struct SDL_ToolkitWindowX11 +{ + /* Locale */ + char *origlocale; + + /* Mode */ + SDL_ToolkitWindowModeX11 mode; + + /* Display */ + Display *display; + int screen; + bool display_close; + + /* Parent */ + SDL_Window *parent; + struct SDL_ToolkitWindowX11 *tk_parent; + + /* Window */ + Window window; + Drawable drawable; + + /* Visuals and drawing */ + Visual *visual; + XVisualInfo vi; + Colormap cmap; + GC ctx; + int depth; + bool pixmap; + + /* X11 extensions */ +#ifdef SDL_VIDEO_DRIVER_X11_XDBE + XdbeBackBuffer buf; + bool xdbe; // Whether Xdbe is present or not +#endif +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + bool xrandr; // Whether Xrandr is present or not +#endif + bool utf8; + + /* Atoms */ + Atom wm_protocols; + Atom wm_delete_message; + + /* Window and pixmap sizes */ + int window_width; // Window width. + int window_height; // Window height. + int pixmap_width; + int pixmap_height; + int window_x; + int window_y; + + /* XSettings and scaling */ + XSettingsClient *xsettings; + bool xsettings_first_time; + int iscale; + float scale; + + /* Font */ + XFontSet font_set; // for UTF-8 systems + XFontStruct *font_struct; // Latin1 (ASCII) fallback. + + /* Control colors */ + const SDL_MessageBoxColor *color_hints; + XColor xcolor[SDL_MESSAGEBOX_COLOR_COUNT]; + XColor xcolor_bevel_l1; + XColor xcolor_bevel_l2; + XColor xcolor_bevel_d; + XColor xcolor_pressed; + XColor xcolor_disabled_text; + + /* Control list */ + bool has_focus; + struct SDL_ToolkitControlX11 *focused_control; + struct SDL_ToolkitControlX11 *fiddled_control; + struct SDL_ToolkitControlX11 **controls; + size_t controls_sz; + struct SDL_ToolkitControlX11 **dyn_controls; + size_t dyn_controls_sz; + + /* User callbacks */ + void *cb_data; + void (*cb_on_scale_change)(struct SDL_ToolkitWindowX11 *, void *); + + /* Popup windows */ + SDL_ListNode *popup_windows; + + /* Event loop */ + XEvent *e; + struct SDL_ToolkitControlX11 *previous_control; + struct SDL_ToolkitControlX11 *key_control_esc; + struct SDL_ToolkitControlX11 *key_control_enter; + KeySym last_key_pressed; + int ev_i; + float ev_scale; + float ev_iscale; + bool draw; + bool close; + long event_mask; +} SDL_ToolkitWindowX11; + +typedef enum SDL_ToolkitControlStateX11 +{ + SDL_TOOLKIT_CONTROL_STATE_X11_NORMAL, + SDL_TOOLKIT_CONTROL_STATE_X11_HOVER, + SDL_TOOLKIT_CONTROL_STATE_X11_PRESSED, /* Key/Button Up */ + SDL_TOOLKIT_CONTROL_STATE_X11_PRESSED_HELD, /* Key/Button Down */ + SDL_TOOLKIT_CONTROL_STATE_X11_DISABLED +} SDL_ToolkitControlStateX11; + +typedef struct SDL_ToolkitControlX11 +{ + SDL_ToolkitWindowX11 *window; + SDL_ToolkitControlStateX11 state; + SDL_Rect rect; + bool selected; + bool dynamic; + bool is_default_enter; + bool is_default_esc; + + /* User data */ + void *data; + + /* Virtual functions */ + void (*func_draw)(struct SDL_ToolkitControlX11 *); + void (*func_calc_size)(struct SDL_ToolkitControlX11 *); + void (*func_on_scale_change)(struct SDL_ToolkitControlX11 *); + void (*func_on_state_change)(struct SDL_ToolkitControlX11 *); + void (*func_free)(struct SDL_ToolkitControlX11 *); +} SDL_ToolkitControlX11; + +typedef struct SDL_ToolkitMenuItemX11 +{ + const char *utf8; + bool checkbox; + bool checked; + bool disabled; + void *cb_data; + void (*cb)(struct SDL_ToolkitMenuItemX11 *, void *); + SDL_ListNode *sub_menu; + + /* Internal use */ + SDL_Rect utf8_rect; + SDL_Rect hover_rect; + SDL_Rect check_rect; + SDL_ToolkitControlStateX11 state; + int arrow_x; + int arrow_y; + bool reverse_arrows; +} SDL_ToolkitMenuItemX11; + +/* WINDOW FUNCTIONS */ +extern SDL_ToolkitWindowX11 *X11Toolkit_CreateWindowStruct(SDL_Window *parent, SDL_ToolkitWindowX11 *tkparent, SDL_ToolkitWindowModeX11 mode, const SDL_MessageBoxColor *colorhints); +extern bool X11Toolkit_CreateWindowRes(SDL_ToolkitWindowX11 *data, int w, int h, int cx, int cy, char *title); +extern void X11Toolkit_DoWindowEventLoop(SDL_ToolkitWindowX11 *data); +extern void X11Toolkit_ResizeWindow(SDL_ToolkitWindowX11 *data, int w, int h); +extern void X11Toolkit_DestroyWindow(SDL_ToolkitWindowX11 *data); +extern void X11Toolkit_SignalWindowClose(SDL_ToolkitWindowX11 *data); + +/* GENERIC CONTROL FUNCTIONS */ +extern bool X11Toolkit_NotifyControlOfSizeChange(SDL_ToolkitControlX11 *control); + +/* ICON CONTROL FUNCTIONS */ +extern SDL_ToolkitControlX11 *X11Toolkit_CreateIconControl(SDL_ToolkitWindowX11 *window, SDL_MessageBoxFlags flags); +extern int X11Toolkit_GetIconControlCharY(SDL_ToolkitControlX11 *control); + +/* LABEL CONTROL FUNCTIONS */ +extern SDL_ToolkitControlX11 *X11Toolkit_CreateLabelControl(SDL_ToolkitWindowX11 *window, char *utf8); + +/* BUTTON CONTROL FUNCTIONS */ +extern SDL_ToolkitControlX11 *X11Toolkit_CreateButtonControl(SDL_ToolkitWindowX11 *window, const SDL_MessageBoxButtonData *data); +extern void X11Toolkit_RegisterCallbackForButtonControl(SDL_ToolkitControlX11 *control, void *data, void (*cb)(struct SDL_ToolkitControlX11 *, void *)); +extern const SDL_MessageBoxButtonData *X11Toolkit_GetButtonControlData(SDL_ToolkitControlX11 *control); + +#endif // SDL_VIDEO_DRIVER_X11 + +#endif // SDL_x11toolkit_h_ diff --git a/src/video/x11/SDL_x11vulkan.c b/src/video/x11/SDL_x11vulkan.c index cf15a076d7..5cc7222838 100644 --- a/src/video/x11/SDL_x11vulkan.c +++ b/src/video/x11/SDL_x11vulkan.c @@ -39,6 +39,20 @@ #define DEFAULT_X11_XCB "libX11-xcb.so.1" #endif +SDL_ELF_NOTE_DLOPEN( + "x11-vulkan", + "Support for vulkan on X11", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + DEFAULT_VULKAN +); + +SDL_ELF_NOTE_DLOPEN( + "x11-vulkan", + "Support for vulkan on X11", + SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + DEFAULT_X11_XCB +); + /* typedef uint32_t xcb_window_t; typedef uint32_t xcb_visualid_t; diff --git a/src/video/x11/SDL_x11xinput2.c b/src/video/x11/SDL_x11xinput2.c index 11afba68ef..3e51408625 100644 --- a/src/video/x11/SDL_x11xinput2.c +++ b/src/video/x11/SDL_x11xinput2.c @@ -74,9 +74,13 @@ static bool xinput2_scrolling_supported; static void parse_relative_valuators(SDL_XInput2DeviceInfo *devinfo, const XIRawEvent *rawev) { + SDL_Mouse *mouse = SDL_GetMouse(); double processed_coords[2] = { 0.0, 0.0 }; int values_i = 0, found = 0; + // Use the raw values if a custom transform function is set, or the relative system scale hint is unset. + const bool use_raw_vals = mouse->InputTransform || !mouse->enable_relative_system_scale; + for (int i = 0; i < rawev->valuators.mask_len * 8 && found < 2; ++i) { if (!XIMaskIsSet(rawev->valuators.mask, i)) { continue; @@ -84,7 +88,7 @@ static void parse_relative_valuators(SDL_XInput2DeviceInfo *devinfo, const XIRaw for (int j = 0; j < 2; ++j) { if (devinfo->number[j] == i) { - double current_val = rawev->raw_values[values_i]; + const double current_val = use_raw_vals ? rawev->raw_values[values_i] : rawev->valuators.values[values_i]; if (devinfo->relative[j]) { processed_coords[j] = current_val; @@ -102,7 +106,6 @@ static void parse_relative_valuators(SDL_XInput2DeviceInfo *devinfo, const XIRaw } // Relative mouse motion is delivered to the window with keyboard focus - SDL_Mouse *mouse = SDL_GetMouse(); if (mouse->relative_mode && SDL_GetKeyboardFocus()) { SDL_SendMouseMotion(rawev->time, mouse->focus, (SDL_MouseID)rawev->sourceid, true, (float)processed_coords[0], (float)processed_coords[1]); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 40156a6ef9..9a985971a9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -394,6 +394,7 @@ if(EMSCRIPTEN) target_link_options(testshader PRIVATE "-sLEGACY_GL_EMULATION") endif() add_sdl_test_executable(testshape NEEDS_RESOURCES SOURCES testshape.c ${glass_bmp_header} DEPENDS generate-glass_bmp_header) +add_sdl_test_executable(testsoftwaretransparent SOURCES testsoftwaretransparent.c) add_sdl_test_executable(testsprite MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testsprite.c) add_sdl_test_executable(testspriteminimal SOURCES testspriteminimal.c ${icon_bmp_header} DEPENDS generate-icon_bmp_header) add_sdl_test_executable(testspritesurface SOURCES testspritesurface.c ${icon_bmp_header} DEPENDS generate-icon_bmp_header) diff --git a/test/android/cmake/AndroidManifest.xml.cmake b/test/android/cmake/AndroidManifest.xml.cmake index 06d87af96c..17b866e51b 100644 --- a/test/android/cmake/AndroidManifest.xml.cmake +++ b/test/android/cmake/AndroidManifest.xml.cmake @@ -44,6 +44,7 @@ android:label="@string/label" android:supportsRtl="true" android:theme="@style/AppTheme" + android:enableOnBackInvokedCallback="false" android:hardwareAccelerated="true"> w / rect->h; - float fovScaleY = SDL_tanf((verticalFOV_deg * SDL_PI_F / 180.0f) * 0.5f); + float fovScaleY = SDL_tanf(((verticalFOV_deg / 180.0f) * SDL_PI_F) * 0.5f); float fovScaleX = fovScaleY * aspect; float relZ = cameraZ - v->z; diff --git a/test/testautomation_events.c b/test/testautomation_events.c index ed4e684cff..ccf417c381 100644 --- a/test/testautomation_events.c +++ b/test/testautomation_events.c @@ -230,6 +230,7 @@ static int SDLCALL IncrementCounterThread(void *userdata) SDL_Event event; SDL_assert(!SDL_IsMainThread()); + SDL_zero(event); if (data->delay > 0) { SDL_Delay(data->delay); diff --git a/test/testdialog.c b/test/testdialog.c index dcff5bd9f0..37d005f4c5 100644 --- a/test/testdialog.c +++ b/test/testdialog.c @@ -12,6 +12,7 @@ /* Sample program: Create open and save dialogs. */ #include +#include #include #include @@ -23,6 +24,8 @@ const SDL_DialogFileFilter filters[] = { }; static void SDLCALL callback(void *userdata, const char * const *files, int filter) { + char **saved_path = userdata; + if (files) { const char* filter_name = "(filter fetching unsupported)"; @@ -36,6 +39,13 @@ static void SDLCALL callback(void *userdata, const char * const *files, int filt SDL_Log("Filter used: '%s'", filter_name); + if (*files && saved_path) { + *saved_path = SDL_strdup(*files); + /* Create the file */ + SDL_IOStream *stream = SDL_IOFromFile(*saved_path, "w"); + SDL_CloseIO(stream); + } + while (*files) { SDL_Log("'%s'", *files); files++; @@ -45,6 +55,23 @@ static void SDLCALL callback(void *userdata, const char * const *files, int filt } } +char *concat_strings(const char *a, const char *b) +{ + char *out = NULL; + + if (a != NULL && b != NULL) { + const size_t out_size = SDL_strlen(a) + SDL_strlen(b) + 1; + out = (char *)SDL_malloc(out_size); + if (out) { + *out = '\0'; + SDL_strlcat(out, a, out_size); + SDL_strlcat(out, b, out_size); + } + } + + return out; +} + int main(int argc, char *argv[]) { SDL_Window *w; @@ -54,7 +81,9 @@ int main(int argc, char *argv[]) const SDL_FRect save_file_rect = { 50, 290, 220, 140 }; const SDL_FRect open_folder_rect = { 370, 50, 220, 140 }; int i; + const char *default_filename = "Untitled.index"; const char *initial_path = NULL; + char *last_saved_path = NULL; /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); @@ -116,7 +145,14 @@ int main(int argc, char *argv[]) } else if (SDL_PointInRectFloat(&p, &open_folder_rect)) { SDL_ShowOpenFolderDialog(callback, NULL, w, initial_path, 1); } else if (SDL_PointInRectFloat(&p, &save_file_rect)) { - SDL_ShowSaveFileDialog(callback, NULL, w, filters, SDL_arraysize(filters), initial_path); + char *save_path = NULL; + if (last_saved_path) { + save_path = SDL_strdup(last_saved_path); + } else { + save_path = concat_strings(initial_path, default_filename); + } + SDL_ShowSaveFileDialog(callback, &last_saved_path, w, filters, SDL_arraysize(filters), save_path ? save_path : default_filename); + SDL_free(save_path); } } } @@ -145,6 +181,7 @@ int main(int argc, char *argv[]) SDL_RenderPresent(r); } + SDL_free(last_saved_path); SDLTest_CleanupTextDrawing(); SDL_DestroyRenderer(r); SDL_DestroyWindow(w); diff --git a/test/testffmpeg_vulkan.c b/test/testffmpeg_vulkan.c index b743fde41a..5a392a905a 100644 --- a/test/testffmpeg_vulkan.c +++ b/test/testffmpeg_vulkan.c @@ -679,7 +679,7 @@ void SetupVulkanRenderProperties(VulkanVideoContext *context, SDL_PropertiesID p SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER, context->graphicsQueueFamilyIndex); } -#if LIBAVUTIL_VERSION_MAJOR >= 59 +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(59, 34, 100) static void AddQueueFamily(AVVulkanDeviceContext *ctx, int idx, int num, VkQueueFlagBits flags) { AVVulkanDeviceQueueFamily *entry = &ctx->qf[ctx->nb_qf++]; @@ -687,7 +687,7 @@ static void AddQueueFamily(AVVulkanDeviceContext *ctx, int idx, int num, VkQueue entry->num = num; entry->flags = flags; } -#endif /* LIBAVUTIL_VERSION_MAJOR */ +#endif /* LIBAVUTIL_VERSION_INT */ void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceContext *ctx) { @@ -700,7 +700,7 @@ void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceCon ctx->nb_enabled_inst_extensions = context->instanceExtensionsCount; ctx->enabled_dev_extensions = context->deviceExtensions; ctx->nb_enabled_dev_extensions = context->deviceExtensionsCount; -#if LIBAVUTIL_VERSION_MAJOR >= 59 +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(59, 34, 100) AddQueueFamily(ctx, context->graphicsQueueFamilyIndex, context->graphicsQueueCount, VK_QUEUE_GRAPHICS_BIT); AddQueueFamily(ctx, context->transferQueueFamilyIndex, context->transferQueueCount, VK_QUEUE_TRANSFER_BIT); AddQueueFamily(ctx, context->computeQueueFamilyIndex, context->computeQueueCount, VK_QUEUE_COMPUTE_BIT); @@ -716,7 +716,7 @@ void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceCon ctx->nb_encode_queues = 0; ctx->queue_family_decode_index = context->decodeQueueFamilyIndex; ctx->nb_decode_queues = context->decodeQueueCount; -#endif /* LIBAVUTIL_VERSION_MAJOR */ +#endif /* LIBAVUTIL_VERSION_INT */ } static int CreateCommandBuffers(VulkanVideoContext *context, SDL_Renderer *renderer) diff --git a/test/testgles2.c b/test/testgles2.c index ba3e0ddcda..2f054fcaf6 100644 --- a/test/testgles2.c +++ b/test/testgles2.c @@ -179,7 +179,7 @@ perspective_matrix(float fovy, float aspect, float znear, float zfar, float *r) int i; float f; - f = 1.0f / SDL_tanf(fovy * 0.5f); + f = 1.0f / SDL_tanf((fovy / 180.0f) * SDL_PI_F * 0.5f); for (i = 0; i < 16; i++) { r[i] = 0.0; diff --git a/test/testgpu_spinning_cube.c b/test/testgpu_spinning_cube.c index 0eeb034fbb..7ec4aeec00 100644 --- a/test/testgpu_spinning_cube.c +++ b/test/testgpu_spinning_cube.c @@ -133,7 +133,7 @@ perspective_matrix(float fovy, float aspect, float znear, float zfar, float *r) int i; float f; - f = 1.0f/SDL_tanf(fovy * 0.5f); + f = 1.0f/SDL_tanf((fovy / 180.0f) * SDL_PI_F * 0.5f); for (i = 0; i < 16; i++) { r[i] = 0.0; diff --git a/test/testgpurender_effects.c b/test/testgpurender_effects.c index b10700b743..5210a327f4 100644 --- a/test/testgpurender_effects.c +++ b/test/testgpurender_effects.c @@ -46,7 +46,7 @@ typedef enum NUM_EFFECTS } FullscreenEffect; -typedef struct +typedef struct { const char *name; const unsigned char *dxil_shader_source; @@ -61,7 +61,7 @@ typedef struct SDL_GPURenderState *state; } FullscreenEffectData; -typedef struct +typedef struct { float texture_width; float texture_height; @@ -145,7 +145,7 @@ static bool InitGPURenderState(void) { SDL_GPUShaderFormat formats; SDL_GPUShaderCreateInfo info; - SDL_GPURenderStateDesc desc; + SDL_GPURenderStateCreateInfo createinfo; int i; formats = SDL_GetGPUShaderFormats(device); @@ -187,9 +187,9 @@ static bool InitGPURenderState(void) return false; } - SDL_INIT_INTERFACE(&desc); - desc.fragment_shader = data->shader; - data->state = SDL_CreateGPURenderState(renderer, &desc); + SDL_zero(createinfo); + createinfo.fragment_shader = data->shader; + data->state = SDL_CreateGPURenderState(renderer, &createinfo); if (!data->state) { SDL_Log("Couldn't create render state: %s", SDL_GetError()); return false; diff --git a/test/testgpurender_msdf.c b/test/testgpurender_msdf.c index 299d8beb03..d28944aa37 100644 --- a/test/testgpurender_msdf.c +++ b/test/testgpurender_msdf.c @@ -164,7 +164,7 @@ static bool InitGPURenderState(void) { SDL_GPUShaderFormat formats; SDL_GPUShaderCreateInfo info; - SDL_GPURenderStateDesc desc; + SDL_GPURenderStateCreateInfo createinfo; MSDFShaderUniforms uniforms; device = (SDL_GPUDevice *)SDL_GetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_GPU_DEVICE_POINTER, NULL); @@ -205,9 +205,9 @@ static bool InitGPURenderState(void) return false; } - SDL_INIT_INTERFACE(&desc); - desc.fragment_shader = shader; - render_state = SDL_CreateGPURenderState(renderer, &desc); + SDL_zero(createinfo); + createinfo.fragment_shader = shader; + render_state = SDL_CreateGPURenderState(renderer, &createinfo); if (!render_state) { SDL_Log("Couldn't create render state: %s", SDL_GetError()); return false; diff --git a/test/testsoftwaretransparent.c b/test/testsoftwaretransparent.c new file mode 100644 index 0000000000..90b78a61d4 --- /dev/null +++ b/test/testsoftwaretransparent.c @@ -0,0 +1,110 @@ +/* + 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. +*/ +#include +#include + +#define SQUARE_SIZE 100.0f + +/* Draw opaque red squares at the four corners of the form, and draw a red square with an alpha value of 180 in the center of the form */ +static void draw(SDL_Renderer *renderer) +{ + SDL_FRect rect = { 0.0f, 0.0f, SQUARE_SIZE, SQUARE_SIZE }; + int w, h; + + SDL_GetCurrentRenderOutputSize(renderer, &w, &h); + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); + SDL_RenderClear(renderer); + + if (w >= 3 * SQUARE_SIZE && h >= 3 * SQUARE_SIZE) { + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + + rect.x = 0.0f; + rect.y = 0.0f; + SDL_RenderFillRect(renderer, &rect); + + rect.y = h - SQUARE_SIZE; + SDL_RenderFillRect(renderer, &rect); + + rect.x = w - SQUARE_SIZE; + SDL_RenderFillRect(renderer, &rect); + + rect.y = 0.0f; + SDL_RenderFillRect(renderer, &rect); + } + + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 180); + rect.x = (w - SQUARE_SIZE) / 2; + rect.y = (h - SQUARE_SIZE) / 2; + SDL_RenderFillRect(renderer, &rect); +} + +int main(int argc, char *argv[]) +{ + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + bool done = false; + SDL_Event event; + + int return_code = 1; + + window = SDL_CreateWindow("SDL Software Renderer Transparent Test", 800, 600, SDL_WINDOW_TRANSPARENT | SDL_WINDOW_RESIZABLE); + if (!window) { + SDL_Log("Couldn't create transparent window: %s", SDL_GetError()); + goto quit; + } + + /* Create a software renderer */ + renderer = SDL_CreateRenderer(window, SDL_SOFTWARE_RENDERER); + if (!renderer) { + SDL_Log("Couldn't create renderer: %s", SDL_GetError()); + goto quit; + } + + /* Make sure we're setting the alpha channel while drawing */ + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); + + /* We're ready to go! */ + while (!done) { + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_EVENT_KEY_DOWN: + if (event.key.key == SDLK_ESCAPE) { + done = true; + } + break; + case SDL_EVENT_WINDOW_EXPOSED: + /* The software renderer is persistent, so only redraw as-needed */ + draw(renderer); + break; + case SDL_EVENT_QUIT: + done = true; + break; + default: + break; + } + } + + /* Show everything on the screen and wait a bit */ + SDL_RenderPresent(renderer); + SDL_Delay(100); + } + + /* Success! */ + return_code = 0; + +quit: + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return return_code; +}